65.9K
CodeProject 正在变化。 阅读更多。
Home

使用Ansible部署Ruby应用程序

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2016年3月18日

MIT

4分钟阅读

viewsIcon

12946

了解如何为您的Ruby应用程序实现配置脚本

引言

如今,您可以在几秒钟内启动并运行您自己的专用服务器。一旦启动,您真的会花费几个小时来配置它以满足您的应用程序需求吗?您真的想对每个新服务器重复相同的步骤吗?在本文中,我将向您介绍使用Ansible(一个简单的IT自动化工具包)和Ubuntu 14.04 LTS服务器作为操作系统来进行自动化安装的想法。

背景

您需要对ansible文件的语法有基本的了解。如果您还没有使用过Ansible,我建议您查看一些介绍性文章,例如https://docs.ansible.org.cn/ansible/intro.html,或者查看类似这个的幻灯片。

烹饪

我们需要部署以下组件:Ruby、带有Passenger的Web服务器、您自己的应用程序。为了演示目的,我们将安装一个众所周知的入门程序Devise

Ruby切换器 Chruby

我过去常常选择RMV,但最近,我成了chruby的粉丝——它轻量级、易于理解并且确实有效——可在Github上找到。

在研究安装说明后,我们使用Ansible自动化手动安装步骤,并获得一个奖励:可重用的配方来安装ch_ruby

---
  - name: Ruby | Check if chruby is present
    shell: test -x /usr/local/bin/chruby-exec
    when: ansible_system == "Linux"
    ignore_errors: yes
    register: chruby_present
    tags: ruby

  - name: Ruby | Download chruby distribution
    get_url: url="http://github.com/postmodern/chruby/archive/v{{ chruby_version }}.tar.gz"
             dest="/tmp/chruby-{{ chruby_version }}.tar.gz"
    when: chruby_present|failed
    tags: ruby

  - name: Ruby | unpack chruby
    command: tar xf "/tmp/chruby-{{ chruby_version }}.tar.gz"
             chdir="/tmp"
    when: chruby_present|failed
    tags: ruby

  - name: Ruby | chruby install target
    command: make install
             chdir="/tmp/chruby-{{ chruby_version }}"
    become: yes
    when: chruby_present|failed
    tags: ruby

  - name: Ruby | autoload script
    template: src="{{role_dir}}/templates/ch_ruby.sh.j2" dest=/etc/profile.d/chruby.sh
    become: yes
    tags: ruby

Ruby 安装

Ruby-install是来自同一作者的第二个工具包。目前,当我需要安装特定的Ruby版本时,这种方法是我的首选。该工具可在Github上找到。

一旦我们研究了ruby-install的设置说明,我们就可以使用一系列ansible步骤来自动化这些步骤

---
  - name: Ruby | Check if ruby install is present
    shell: test -x /usr/local/bin/ruby-install
    when: ansible_system == "Linux"
    ignore_errors: yes
    register: rubyinstall_present
    tags: ruby

  - name: Ruby | Ruby install | package dependencies
    apt: pkg={{ item }} state=present force="yes"  update_cache="yes"
    when: ansible_system == "Linux"
    with_items:
      - build-essential
      - libffi-dev
      - libgdbm-dev
      - libncurses5-dev
      - libreadline-dev
      - libreadline6-dev
      - libtinfo-dev
      - libyaml-dev
    become: yes
    tags: ruby


  - name: Ruby | Download rubyinstall
    get_url: url=http://github.com/postmodern/ruby-install/archive/v{{ ruby_install_version }}.tar.gz
           dest=/tmp/ruby-install-{{ ruby_install_version }}.tar.gz
    when: rubyinstall_present | failed
    tags: ruby

  - name: Ruby | Unpack ruby-install
    command: tar xf /tmp/ruby-install-{{ ruby_install_version }}.tar.gz
             chdir=/tmp
    when: rubyinstall_present | failed
    tags: ruby

  - name: Ruby | Run ruby-install install target
    command: make install
           chdir=/tmp/ruby-install-{{ ruby_install_version }}
    when: rubyinstall_present | failed
    become: yes
    tags: ruby

  - name: Ruby | Download list of rubies available
    command: ruby-install
    when: rubyinstall_present | failed
    become: yes
    tags: ruby

Ruby

现在是时候安装Ruby了。这里有一个小评论:如果您部署在共享服务器上,您很可能希望能够拥有多个ruby版本并在它们之间切换。另一方面,如果您将应用程序部署到专用主机上,通常我也会用相同的ruby版本替换默认的系统ruby。

使用上述工具,Ruby安装配方紧凑而清晰

---
  - name: Ruby | Find out if ruby_version is already installed
    stat: path={{rubies_location}}/ruby-{{ruby_version}}
    register: ruby_version_present
    tags: ruby

  - name: Ruby | Install ruby_version if necessary
    command: '/usr/local/bin/ruby-install ruby {{ruby_version}}'
    when: not ruby_version_present.stat.exists
    become: yes
    tags: ruby

  - debug: var="ruby_install_setsystem"

  - name: Ruby | Update SYSTEM ruby_version if necessary
    command: '/usr/local/bin/ruby-install --system ruby {{ruby_version}}'
    when: option_ruby_install_setsystem
    become: yes
    tags: ruby

Web服务器和Passenger

感谢Phusion Passenger团队,他们出色地为大多数流行的平台和配置提供了预构建的二进制文件,请访问此链接。这使我们能够跳过从源代码编译phusion passengers、重新编译Web服务器等的步骤,并使用预构建的二进制文件。

从历史上看,我更喜欢Nginx而不是经典的Apache,因此我们将安装带有passenger的预构建Nginx

---
  - name: Nginx | Check if is present
    command: test -x /usr/sbin/nginx
    when: ansible_os_family == "Debian"
    ignore_errors: yes
    register: nginx_present
    tags: nginx

  - name: Passenger | Add GPG key to apt keyring
    apt_key: keyserver=keyserver.ubuntu.com id=561F9B9CAC40B2F7
    when: ansible_os_family == "Debian" and nginx_present|failed
    tags: passenger
    become: yes

  - name: Passenger | Install needed packages
    apt: state=present pkg="{{item}}"
    with_items:
     - apt-transport-https
     - ca-certificates
    when: ansible_os_family == "Debian" and nginx_present|failed
    become: yes
    tags: passenger

  - name: Passenger | Add nginx extras repository
    apt_repository: repo="deb https://oss-binaries.phusionpassenger.com/apt/passenger trusty main" state=present
    when: ansible_os_family == "Debian" and nginx_present|failed
    tags: passenger
    become: yes

  - name: Ruby | Install Nginx extra and Phusion Passenger
    apt: state=present update_cache=yes pkg="{{item}}"
    when: ansible_os_family == "Debian" and nginx_present|failed
    with_items:
     - nginx-extras
     - passenger
    become: yes
    tags: passenger

  - name: Nginx | Create sites available/enabled directories
    file: path={{item}} state=directory mode=0755
    with_items:
      - /etc/nginx/sites-available
      - /etc/nginx/sites-enabled
    when: ansible_os_family == "Debian" and nginx_present|failed
    tags:
      - nginx
      - passenger
    become: yes

  - name: Nginx | Configure include sites-enabled
    lineinfile: dest=/etc/nginx/nginx.conf regexp=".*sites-enabled.*" 
    line="    include /etc/nginx/sites-enabled/*;" insertbefore="}" state=present
    tags:
      - nginx
      - passenger
    when: ansible_os_family == "Debian" and nginx_present|failed
    become: yes

  - name: Nginx | Disable default site
    file: path=/etc/nginx/sites-enabled/default state=absent
    tags:
      - nginx
      - passenger
    when: ansible_os_family == "Debian" and nginx_present|failed
    become: yes

  - name: Nginx | Uncomment server_names_hash_bucket_size
    lineinfile: dest=/etc/nginx/nginx.conf 
    regexp="^(\s*)#\s*server_names_hash_bucket_size" 
    line="\1server_names_hash_bucket_size 64;" backrefs=yes
    become: yes
    when: ansible_os_family == "Debian" and nginx_present|failed
    tags:
      - nginx
      - passenger

  - name: Nginx | Set ruby to system one
    lineinfile: dest=/etc/nginx/nginx.conf regexp="^(\s*)#\s*passenger_ruby" 
                line="passenger_ruby /usr/local/bin/ruby;" backrefs=yes
    become: yes
    when: ansible_os_family == "Debian" and nginx_present|failed
    tags:
      - nginx
      - passenger

  - name: Nginx | Set ruby to system one
    lineinfile: dest=/etc/nginx/nginx.conf regexp="^(\s*)#\s*passenger_root" 
    line="passenger_root /usr/lib/ruby/vendor_ruby/phusion_passenger/locations.ini;" 
    backrefs=yes
    become: yes
    when: ansible_os_family == "Debian" and nginx_present|failed
    tags:
      - nginx
      - passenger

  - name: Nginx | Reload
    service: name=nginx state=reloaded
    when: ansible_os_family == "Debian" and nginx_present|failed
    tags:
      - nginx
      - passenger
    become: yes

上面安装脚本的一些评论,这可能需要在您自己的场景中进行更改

  1. 名为“创建sites available/enabled目录”的任务以及下一个任务——它实际上为VHosts配置实现了类似Apache的“sites-available / sites-enabled”文件夹结构。如果您更喜欢默认设置 - 请将其注释掉。
  2. 使用以下命令指定ruby的位置给passenger
    lineinfile: dest=/etc/nginx/nginx.conf regexp="^(\s*)#\s*passenger_ruby" 
    line="passenger_ruby /usr/local/bin/ruby;" backrefs=yes

如您所见,上面的替换假定使用了系统ruby。您可能希望在此处指定不同的Ruby路径。

这两个任务的目标是获取nginx.conf并设置两个参数:passenger_rootpassenger_ruby,如上述注释中的原始说明所示。

        ##
        # Uncomment it if you installed passenger or passenger-enterprise
        ##

passenger_root /usr/lib/ruby/vendor_ruby/phusion_passenger/locations.ini;
passenger_ruby /usr/local/bin/ruby;

如何验证您是否正确安装了nginx和passenger?

执行这些命令并验证设置

sudo /usr/bin/passenger-config validate-install
What would you like to validate?
Use <space> to select.
If the menu doesn't display correctly, press '!'

 ‣ ⬢  Passenger itself
   ⬡  Apache

-------------------------------------------------------------------------

 * Checking whether this Passenger install is in PATH... ✓
 * Checking whether there are no other Passenger installations... ✓

Everything looks good. :-)

以及/usr/sbin/passenger-memory-stats - 您应该同时看到 - Nginx 和 passenger 进程。

sudo /usr/sbin/passenger-memory-stats
Version: 5.0.26
Date   : 2016-03-18 11:17:57 +0200
------------- Apache processes -------------
*** WARNING: The Apache executable cannot be found.
Please set the APXS2 environment variable to your 'apxs2' executable's filename, 
or set the HTTPD environment variable to your 'httpd' or 'apache2' executable's filename.


--------- Nginx processes ----------
PID   PPID  VMSize    Private  Name
------------------------------------
8768  9991  138.1 MB  1.1 MB   nginx: worker process
8769  9991  137.8 MB  0.9 MB   nginx: worker process
8770  9991  137.8 MB  0.9 MB   nginx: worker process
8771  9991  137.8 MB  0.9 MB   nginx: worker process
9991  1     137.8 MB  0.9 MB   nginx: master process /usr/sbin/nginx
### Processes: 5
### Total private dirty RSS: 4.68 MB


---- Passenger processes -----
PID   VMSize    Private  Name
------------------------------
8742  436.3 MB  1.0 MB   Passenger watchdog
8745  982.9 MB  2.0 MB   Passenger core
8756  444.5 MB  1.1 MB   Passenger ust-router
8806  387.1 MB  69.3 MB  Passenger RubyApp: /var/www/public (production)
### Processes: 4
### Total private dirty RSS: 73.47 MB
slavko@ERM:/etc/nginx$ 

应用程序本身的设置

让我们定义应用程序参数:特别是:构建gem所需的操作系统软件包、应用程序密钥 - 用于对密码进行哈希处理、应用程序环境参数、数据库连接详细信息。

  app_dependencies:
    - libsqlite3-dev
    - libmysqlclient-dev
    - libpq-dev
    - git
    - nodejs
    - npm

  app_short_name: app
  app_env: production
  app_domain: domain.local
  app_secret: 82d58d3dfb91238b495a311eb8539edf5064784f1d58994679db8363ec241c745bef0b446bfe44d66cbf91a2f4e497d8f6b1ef1656e3f405b0d263a9617ac75e
  app_repository: https://github.com/RailsApps/rails-devise.git
#  app_repository_keyname: id_rsa_app
  app_base_dir: /var/www
  app_www_root: "{{app_base_dir}}/public"

  app_env_vars:
    - {name: SECRET_KEY_BASE, value: "{{app_secret}}" }
    - {name: DATABASE_URL, value: "postgres://{{app_db_user}}:
      {{app_db_password}}@{{app_db_host}}/{{app_db_name}}"}
    - {name: RAILS_ENV, value: "{{app_env}}" }
    - {name: DOMAIN_NAME, value: "{{app_domain}}" } 

  app_db_host: localhost
  app_db_user: app_user
  app_db_password: app_password
  app_db_name: app_database    

  app_directories:
    - "{{app_base_dir}}"

以及应用程序配置脚本本身,它分为几个阶段:操作系统软件包依赖项、Gem依赖项(对于devise,它是sqlite3)、检出源代码、修补Gem文件,以便ruby版本与主机上安装的版本匹配 + 引入用于uglifyjs的生产gem(这是当前应用程序的特定内容)、bundle install、修补数据库配置、资产编译、数据库迁移、生成nginx站点配置、重启Web服务器。

---

  - name: APP STUB | Dependencies
    apt: pkg={{ item }} state=present force="yes"  update_cache="yes"
    when: ansible_system == "Linux"
    with_items: "{{app_dependencies}}"
    become: yes
    tags: app_stub

  - name: APP STUB | Install gem dependencies
    shell: "gem install --no-rdoc --no-ri {{item}}"
    with_items:
      - sqlite3
    become: yes
    tags: app_stub

  - name: APP STUB | Re-create base app directory
    file: path={{app_base_dir}} state=absent
    become: yes
    tags: app_stub

  - name: APP STUB | Create directories
    file: path={{item}} state=directory mode=0755 
          owner={{ansible_user_id}} group={{ansible_user_id}}
    with_items: "{{app_directories}}"
    become: yes
    tags: app_stub

  - name: APP STUB | Checkout app without key
    git: repo="{{app_repository}}" dest="{{app_base_dir}}" 
         accept_hostkey="yes" force="yes"
    when: app_repository_keyname is not defined
    tags: app_stub

  - name: APP STUB | Install global rails gem
    shell: gem install --no-rdoc --no-ri rails
    become: yes
    tags: app_stub

  - name: APP STUB | Eliminate ruby req
    lineinfile: dest="{{app_base_dir}}/Gemfile" regexp="^(\s*)*ruby" 
                line="ruby '{{ruby_version}}'"
    tags: app_stub

  - name: APP STUB | gem therubyracer - uglifyjs
    lineinfile: dest="{{app_base_dir}}/Gemfile" regexp="^(\s*)*gem 'therubyracer'" 
    line="gem 'therubyracer', :platforms => :ruby" insertafter="^group :production do"
    tags: app_stub

  - name: APP STUB | gem execjs - uglifyjs
    lineinfile: dest="{{app_base_dir}}/Gemfile" regexp="^(\s*)*gem 'execjs'" 
    line="gem 'execjs'" insertafter="^group :production do"
    tags: app_stub

  - name: APP STUB | gem pg
    lineinfile: dest="{{app_base_dir}}/Gemfile" regexp="^(\s*)*gem 'pg'" 
    line="gem 'pg'" insertafter="^group :production do"
    tags: app_stub

  - name: APP STUB | Run bundle install --path .bundle/gems --binstubs .bundle/bin
    shell: bundle install  --path .bundle/gems --binstubs .bundle/bin
    args:
      chdir: "{{app_base_dir}}"
    tags: app_stub


  - name: APP STUB | database.yml
    template: src="{{root_dir}}/templates/app/database.yml.j2" 
    dest="{{app_base_dir}}/config/database.yml"
    become: yes
    tags: app_stub


  - name: APP STUB | Precompile assets
    shell: bundle exec rake assets:precompile
    args:
      chdir: "{{app_base_dir}}"
    environment:
      RAILS_ENV: "{{app_env}}"
      DATABASE_URL: "postgres://{{app_db_user}}:{{app_db_password}}@{{app_db_host}}/{{app_db_name}}"
      SECRET_KEY_BASE: "{{app_secret}}"
      DOMAIN_NAME: "{{app_domain}}"
    tags: app_stub

  - name: APP STUB | DB Migrate
    shell: bundle exec rake db:migrate
    args:
      chdir: "{{app_base_dir}}"
    environment:
      RAILS_ENV: "{{app_env}}"
      DATABASE_URL: "postgres://{{app_db_user}}:{{app_db_password}}@{{app_db_host}}/{{app_db_name}}"
      SECRET_KEY_BASE: "{{app_secret}}"
      DOMAIN_NAME: "{{app_domain}}"
    tags: app_stub

  - name: APP STUB | Nginx conf
    template: src="{{root_dir}}/templates/nginx_app.conf.j2" dest="/etc/nginx/sites-enabled/{{app_short_name}}.conf"
    become: yes
    tags: app_stub

  - name: Nginx | Reload
    service: name=nginx state=reloaded
    become: yes
    tags: app_stub

特别是,我们使用实际连接详细信息修补数据库config/database.yml

# On Heroku and other platform providers, you may have a full connection URL
# available as an environment variable. For example:
#
#   DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase"
#
# You can use this database configuration with:
#
   production:
     url: <%= ENV['DATABASE_URL'] %>

并且我们修补 Nginx 应用程序站点配置,以便使用passenger_env_var指令向ruby应用程序提供应用程序环境变量。

server {
  listen 80 default_server;
  passenger_enabled on;

  {% for envvar in app_env_vars %}
  passenger_env_var {{ envvar.name }} "{{ envvar.value }}";
  {% endfor %}

  passenger_app_env {{app_env}};
  root {{app_www_root}};
}

运行代码

让我们执行配置并进行测试,为了演示目的 - 我们将使用本地 postgres 作为数据库。

---
- hosts: www

  vars:
    - root_dir: ..

  roles:
     - {
         role: "sa-postgres",
         option_create_app_user: true
       }
     - {
         role: "sa-ruby",
         ruby_install_setsystem: true,
         ruby_version: 2.3.0,

         option_install_sampleapp: true,
         option_install_nginx_passenger: true
       }

一旦应用程序配置过程完成

TASK: [sa-ruby | Nginx | Reload] ********************************************** 
changed: [192.168.0.17] => {"changed": true, "name": "nginx", "state": "started"}


PLAY RECAP ******************************************************************** 
192.168.0.17               : ok=55   changed=46   unreachable=0    failed=0   

Play run took 23 minutes

因此,根据网络速度,您已安装了您的应用程序。

让我们通过IP地址进行检查

关注点

现在您已经了解了部署Ruby应用程序的另一种方法。

演示部署脚本可在此处找到,作为Ansible可重用角色打包的配方可在此处找到。

© . All rights reserved.