用tsuru部署Ruby应用

概述

本文档是在tsuru中部署一个简单的Ruby应用的实战指南。例子应用是一个关联MySQL服务的简单Rails项目。

在tsuru中创建应用

使用app-create命令创建应用:

  1. $ tsuru app-create <app-name> <app-platform>

对于Ruby来说,应用平台是,你猜,ruby!让我们脑洞打开,开发一个从未有人做过的教学应用:一个博客,而且它的名字也应该是很有创意,让我们叫它blog

  1. $ tsuru app-create blog ruby

使用platform-list命令列出所有的可用的平台。使用app-list查看你所有的应用。

  1. $ tsuru app-list
  2. +-------------+-------------------------+-------------+
  3. | Application | Units State Summary | Address |
  4. +-------------+-------------------------+-------------+
  5. | blog | 0 of 0 units in-service | |
  6. +-------------+-------------------------+-------------+

应用代码

本文档不会专注于介绍如何用Rails实现博客,你可以从GitHub clone整个源代码:https://github.com/tsuru/tsuru-ruby-sample。 下面是我们在项目中所做的事情:

. 创建项目 (rails new blog)

. 为Post生成脚手架(rails generate scaffold Post title:string body:text)

通过Git部署

在创建新的应用时,tsuru会显示应该使用的Git远程分支。用app-info命令可以获得其信息:

  1. $ tsuru app-info --app blog
  2. Application: blog
  3. Repository: git@192.168.50.4.nip.io:blog.git
  4. Platform: ruby
  5. Teams: admin
  6. Address: blog.192.168.50.4.nip.io
  7. Owner: admin@example.com
  8. Team owner: admin
  9. Deploys: 0
  10. Pool: theonepool
  11. App Plan:
  12. +---------------+--------+------+-----------+--------+---------+
  13. | Name | Memory | Swap | Cpu Share | Router | Default |
  14. +---------------+--------+------+-----------+--------+---------+
  15. | autogenerated | 0 MB | 0 MB | 100 | | false |
  16. +---------------+--------+------+-----------+--------+---------+

Git远程分支被用来通过Git部署应用。当修改被推送到tsuru远程分支时,项目同时也被部署:

  1. $ git push git@192.168.50.4.nip.io:blog.git master
  2. Counting objects: 86, done.
  3. Delta compression using up to 4 threads.
  4. Compressing objects: 100% (75/75), done.
  5. Writing objects: 100% (86/86), 29.75 KiB, done.
  6. Total 86 (delta 2), reused 0 (delta 0)
  7. remote: Cloning into '/home/application/current'...
  8. remote: requirements.apt not found.
  9. remote: Skipping...
  10. remote: /home/application/current /
  11. remote: Fetching gem metadata from https://rubygems.org/.........
  12. remote: Fetching gem metadata from https://rubygems.org/..
  13. #####################################
  14. # OMIT (see below) #
  15. #####################################
  16. remote: ---> App will be restarted, please check its log for more details...
  17. remote:
  18. To git@192.168.50.4.nip.io:blog.git
  19. * [new branch] master -> master

如果遇到"Permission denied (publickey)."的错误,请确保你是团队一员并把公钥加到tsuru中。用key-add命令添加公钥:

  1. $ tsuru key-add mykey ~/.ssh/id_rsa.pub

使用git remote add命令来避免每次push代码时都要输入整个远程仓库的链接:

  1. $ git remote add tsuru git@192.168.50.4.nip.io:blog.git

然后运行:

  1. $ git push tsuru master
  2. Everything up-to-date

从此之后就可以省略掉—app标记:

  1. $ tsuru app-info
  2. Application: blog
  3. Repository: git@192.168.50.4.nip.io:blog.git
  4. Platform: ruby
  5. Teams: admin
  6. Address: blog.192.168.50.4.nip.io
  7. Owner: admin@example.com
  8. Team owner: admin
  9. Deploys: 0
  10. Pool: theonepool
  11. Units: 1
  12. +------------+---------+
  13. | Unit | State |
  14. +------------+---------+
  15. | eab5151eff | started |
  16. +------------+---------+
  17. App Plan:
  18. +---------------+--------+------+-----------+--------+---------+
  19. | Name | Memory | Swap | Cpu Share | Router | Default |
  20. +---------------+--------+------+-----------+--------+---------+
  21. | autogenerated | 0 MB | 0 MB | 100 | | false |
  22. +---------------+--------+------+-----------+--------+---------+

列出依赖

在上一个部分我们忽略了部署过程的依赖。在tsuru中,一个应用可以有两种依赖:

  • 操作系统级别的依赖, 以底层操作系统使用的包管理器为代表(比如: yumapt-get);
  • 平台依赖, 以平台/语言依赖的包管理工具为代表(在Ruby中是bundler)。
    所有的apt-get依赖必须在requirements.apt文件中指定,放在应用的根目录,同理,ruby的依赖也必须放在应用根目录名为Gemfile的文件中。因为要用Rails连接MySQL,我们需要用gem安装mysql,这个包依赖于:libmysqlclient-dev,因此requirements.apt内容如下:
  1. libmysqlclient-dev

下面是Gemfile的内容:

  1. source 'https://rubygems.org'
  2. gem 'rails', '3.2.13'
  3. gem 'mysql'
  4. gem 'sass-rails', '~> 3.2.3'
  5. gem 'coffee-rails', '~> 3.2.1'
  6. gem 'therubyracer', platforms: 'ruby'
  7. gem 'uglifier', '>= 1.0.3'
  8. gem 'jquery-rails'

下面是安装这些依赖的完整输出:

  1. $ git push tsuru master
  2. #####################################
  3. # OMIT #
  4. #####################################
  5. remote: Reading package lists...
  6. remote: Building dependency tree...
  7. remote: Reading state information...
  8. remote: The following extra packages will be installed:
  9. remote: libmysqlclient18 mysql-common
  10. remote: The following NEW packages will be installed:
  11. remote: libmysqlclient-dev libmysqlclient18 mysql-common
  12. remote: 0 upgraded, 3 newly installed, 0 to remove and 0 not upgraded.
  13. remote: Need to get 2360 kB of archives.
  14. remote: After this operation, 9289 kB of additional disk space will be used.
  15. remote: Get:1 http://archive.ubuntu.com/ubuntu/ quantal/main mysql-common all 5.5.27-0ubuntu2 [13.7 kB]
  16. remote: Get:2 http://archive.ubuntu.com/ubuntu/ quantal/main libmysqlclient18 amd64 5.5.27-0ubuntu2 [949 kB]
  17. remote: Get:3 http://archive.ubuntu.com/ubuntu/ quantal/main libmysqlclient-dev amd64 5.5.27-0ubuntu2 [1398 kB]
  18. remote: Fetched 2360 kB in 2s (1112 kB/s)
  19. remote: Selecting previously unselected package mysql-common.
  20. remote: (Reading database ... 41063 files and directories currently installed.)
  21. remote: Unpacking mysql-common (from .../mysql-common_5.5.27-0ubuntu2_all.deb) ...
  22. remote: Selecting previously unselected package libmysqlclient18:amd64.
  23. remote: Unpacking libmysqlclient18:amd64 (from .../libmysqlclient18_5.5.27-0ubuntu2_amd64.deb) ...
  24. remote: Selecting previously unselected package libmysqlclient-dev.
  25. remote: Unpacking libmysqlclient-dev (from .../libmysqlclient-dev_5.5.27-0ubuntu2_amd64.deb) ...
  26. remote: Setting up mysql-common (5.5.27-0ubuntu2) ...
  27. remote: Setting up libmysqlclient18:amd64 (5.5.27-0ubuntu2) ...
  28. remote: Setting up libmysqlclient-dev (5.5.27-0ubuntu2) ...
  29. remote: Processing triggers for libc-bin ...
  30. remote: ldconfig deferred processing now taking place
  31. remote: /home/application/current /
  32. remote: Fetching gem metadata from https://rubygems.org/..........
  33. remote: Fetching gem metadata from https://rubygems.org/..
  34. remote: Using rake (10.1.0)
  35. remote: Using i18n (0.6.1)
  36. remote: Using multi_json (1.7.8)
  37. remote: Using activesupport (3.2.13)
  38. remote: Using builder (3.0.4)
  39. remote: Using activemodel (3.2.13)
  40. remote: Using erubis (2.7.0)
  41. remote: Using journey (1.0.4)
  42. remote: Using rack (1.4.5)
  43. remote: Using rack-cache (1.2)
  44. remote: Using rack-test (0.6.2)
  45. remote: Using hike (1.2.3)
  46. remote: Using tilt (1.4.1)
  47. remote: Using sprockets (2.2.2)
  48. remote: Using actionpack (3.2.13)
  49. remote: Using mime-types (1.23)
  50. remote: Using polyglot (0.3.3)
  51. remote: Using treetop (1.4.14)
  52. remote: Using mail (2.5.4)
  53. remote: Using actionmailer (3.2.13)
  54. remote: Using arel (3.0.2)
  55. remote: Using tzinfo (0.3.37)
  56. remote: Using activerecord (3.2.13)
  57. remote: Using activeresource (3.2.13)
  58. remote: Using coffee-script-source (1.6.3)
  59. remote: Using execjs (1.4.0)
  60. remote: Using coffee-script (2.2.0)
  61. remote: Using rack-ssl (1.3.3)
  62. remote: Using json (1.8.0)
  63. remote: Using rdoc (3.12.2)
  64. remote: Using thor (0.18.1)
  65. remote: Using railties (3.2.13)
  66. remote: Using coffee-rails (3.2.2)
  67. remote: Using jquery-rails (3.0.4)
  68. remote: Installing libv8 (3.11.8.17)
  69. remote: Installing mysql (2.9.1)
  70. remote: Using bundler (1.3.5)
  71. remote: Using rails (3.2.13)
  72. remote: Installing ref (1.0.5)
  73. remote: Using sass (3.2.10)
  74. remote: Using sass-rails (3.2.6)
  75. remote: Installing therubyracer (0.11.4)
  76. remote: Installing uglifier (2.1.2)
  77. remote: Your bundle is complete!
  78. remote: Gems in the groups test and development were not installed.
  79. remote: It was installed into ./vendor/bundle
  80. #####################################
  81. # OMIT #
  82. #####################################
  83. To git@192.168.50.4.nip.io:blog.git
  84. 9515685..d67c3cd master -> master

运行应用

如你所见,部署的输出信息中有一个步骤叫做"重启你的应用"。在这个步骤中,如果应用在运行,tsuru会重启它,如果没有运行,tsuru会启动它。但是tsuru是如何启动一个应用呢?非常简单,它使用了一个Procfile(从Foreman中偷学的概念)。这个Procfile用来描述应用如何被启动。如下是Procfile的内容:

  1. web: bundle exec rails server -p $PORT -e production

现在我们提交文件并将其推送到tsuru的git服务器,再运行一次部署任务:

  1. $ git add Procfile
  2. $ git commit -m "Procfile: added file"
  3. $ git push tsuru master
  4. #####################################
  5. # OMIT #
  6. #####################################
  7. remote: ---> App will be restarted, please check its log for more details...
  8. remote:
  9. To git@192.168.50.4.nip.io:blog.git
  10. d67c3cd..f2a5d2d master -> master

应用部署成功,可以通过app-list命令获得IP或者主机名,然后用浏览器去访问。比如,在下面的列表中:

  1. $ tsuru app-list
  2. +-------------+-------------------------+---------------------+
  3. | Application | Units State Summary | Address |
  4. +-------------+-------------------------+---------------------+
  5. | blog | 1 of 1 units in-service | blog.cloud.tsuru.io |
  6. +-------------+-------------------------+---------------------+

使用服务

应用没有成功运行,原因在于rails不能连接到MySQL服务器,而我们在rails应用和MySQL实例之间添加了关联关系。要实现这点,必须使用一个服务.服务的工作流分为下面两步:

. 创建一个服务的实例. 将服务的实例绑定到应用

但是如何知道哪些服务是可用的呢?这很简单,运行service-list命令:

  1. $ tsuru service-list
  2. +----------------+-----------+
  3. | Services | Instances |
  4. +----------------+-----------+
  5. | elastic-search | |
  6. | mysql | |
  7. +----------------+-----------+

上面service-list的输出说明有两个可用的服务:"elastic-search"和"mysql",并且没有实例。通过service-add命令可以创建MySQL实例:

  1. $ tsuru service-add mysql blogsql
  2. Service successfully added.

现在,如果再次运行service-list命令,可以从输出中看到新的服务实例:

  1. $ tsuru service-list
  2. +----------------+-----------+
  3. | Services | Instances |
  4. +----------------+-----------+
  5. | elastic-search | |
  6. | mysql | blogsql |
  7. +----------------+-----------+

使用service-bind命令将服务实例绑定到应用:

  1. $ tsuru service-bind mysql blogsql
  2. Instance blogsql is now bound to the app blog.
  3. The following environment variables are now available for use in your app:
  4. - MYSQL_PORT
  5. - MYSQL_PASSWORD
  6. - MYSQL_USER
  7. - MYSQL_HOST
  8. - MYSQL_DATABASE_NAME
  9. For more details, please check the documentation for the service, using service-doc command.

从绑定的输出中我们看出,可以通过环境变量来连接MySQL服务器。下一步就是更新conf/database.yml去使用这些环境变量去连接数据库:

  1. production:
  2. adapter: mysql
  3. encoding: utf8
  4. database: <%= ENV["MYSQL_DATABASE_NAME"] %>
  5. pool: 5
  6. username: <%= ENV["MYSQL_USER"] %>
  7. password: <%= ENV["MYSQL_PASSWORD"] %>
  8. host: <%= ENV["MYSQL_HOST"] %>

现在提交修改,再次运行部署:

  1. $ git add conf/database.yml
  2. $ git commit -m "database.yml: using environment variables to connect to MySQL"
  3. $ git push tsuru master
  4. Counting objects: 7, done.
  5. Delta compression using up to 4 threads.
  6. Compressing objects: 100% (4/4), done.
  7. Writing objects: 100% (4/4), 535 bytes, done.
  8. Total 4 (delta 3), reused 0 (delta 0)
  9. remote:
  10. remote: ---> tsuru receiving push
  11. remote:
  12. remote: ---> Installing dependencies
  13. #####################################
  14. # OMIT #
  15. #####################################
  16. remote:
  17. remote: ---> Restarting your app
  18. remote:
  19. remote: ---> Deploy done!
  20. remote:
  21. To git@192.168.50.4.nip.io:blog.git
  22. ab4e706..a780de9 master -> master

如果现在尝试访问admin页面,会得到另外一个错误:"Table 'blogsql.django_session' doesn't exist"。这意味着绑定成功,可以访问数据库服务器,但是数据库还没有建立。需要在远程服务器运行rake db:migrate。我们可以使用app-run在机器上执行命令,所以对于运行rake db:migrate,可以这么写:

  1. $ tsuru app-run -- RAILS_ENV=production bundle exec rake db:migrate
  2. == CreatePosts: migrating ====================================================
  3. -- create_table(:posts)
  4. -> 0.1126s
  5. == CreatePosts: migrated (0.1128s) ===========================================

部署钩子

每次部署后手动运行rake db:migrate会很烦人。所以我们可以配置一个自动的钩子,在每次应用重启前或者重启后运行。tsuru会解析一个名为tsuru.yaml的文件,运行重启的钩子。如扩展所建议的那样,这是一个YAML文件,包含了一系列在重启前后要运行的命令。下面是一个tsuru.yaml文件的例子:

  1. hooks:
  2. restart:
  3. before-each:
  4. - RAILS_ENV=production bundle exec rake db:migrate

更多内容,请查看tsuru.yaml中的钩子文档部分。tsuru会在项目的根目录查找这个文件。让我们提交并且部署它:

  1. $ git add tsuru.yaml
  2. $ git commit -m "tsuru.yaml: added file"
  3. $ git push tsuru master
  4. #####################################
  5. # OMIT #
  6. #####################################
  7. To git@192.168.50.4.nip.io:blog.git
  8. a780de9..1b675b8 master -> master

在应用重启前,有必要编译资源文件。可以用rake assets:precompile命令实现。让我们把这个命令加入tsuru.yaml文件中:

  1. hooks:
  2. build:
  3. - RAILS_ENV=production bundle exec rake assets:precompile
  1. $ git add tsuru.yaml
  2. $ git commit -m "tsuru.yaml: added file"
  3. $ git push tsuru master
  4. #####################################
  5. # OMIT #
  6. #####################################
  7. To git@192.168.50.4.nip.io:blog.git
  8. a780de9..1b675b8 master -> master

顺利完成!现在我们有了一个部署在tsuru上,使用MySQL服务的Rails项目,现在我们可以访问app-info返回的blog app的URL了。

进一步探索

更多信息,可以查看tsuru文档,或者阅读tsuru命令完全使用指南

原文: http://doc.oschina.net/tsuru-paas?t=52818