AnsibleとRails-使い慣れた快適さを維持しながら、カピストラーノを柔軟に交換

Capistranoは、多くのRails開発者に愛されているツールであり、これを使用すると、アプリケーションのデプロイメントを迅速かつ簡単に自動化できます。 Capistranoは、RoRデプロイメントシステムのデファクトスタンダードです。これは、PythonやPHPの開発者が一度にうらやむツールである、自尊心のあるハッカーにとって必見のテクノロジーです。

私はあきらめたくはありませんが、より複雑な課題を解決しなければならないほど、カピストラーノは彼らに順応しないことを頻繁に示しました。



私は次の欠点に注意しました。



多くのルビー開発者はMinaに切り替えたか、 ChefPuppetなどのさらに複雑な構成管理システムで問題を解決しました。 それらはすべて独自の特徴と欠点を持ち、さまざまな程度で上記の問題を解決します。 私はAnsibleの助けを借りて、私が慣れていたカピストラーノの利点を失うことなくそれらを解決することができました。



Ansibleは構成管理ツールであり、そのタスクには、この記事で説明する個別のアプリケーションを展開および管理するためのサーバーでのリモートコマンドの実行だけでなく、保存されたサーバー構成(Ansible言語の役割)によるサーバー管理の自動化も含まれます。 Ansible(とにかくChefとPuppetも同様)はCapistranoよりもはるかに多くのことを可能にし、最終的にそれらすべてを彼と比較することはできません。 ただし、この記事の目的は、Rails開発者に移行の出発点を提供し、この例でAnsibleの基本を説明することです。 この記事の最後で、magic cap production deployコマンドはansible-playbook deploy.yml -i inventory / productionに変わります。

誰がどのように気にします-私は猫を求めます。



設置



Ansibleはpythonで書かれています。 すべてのルビストがそれを好むわけではありませんが、すぐに恐怖を払拭します。「敵の言語」で1行も書く必要はありません。 Ansibleの魅力的な機能は、すべての展開スクリプトが、シンプルで強力な記述構文を備えた有名なyml形式の構成ファイルであることです。



ansibleのインストールも迅速かつ簡単です。 ローカルマシンにのみansibleをインストールします。

sudo easy_isntall pip sudo pip install -U ansible
      
      





これは、Pythonユーティリティとの対話が終了する場所であり、 ansible-playbookコマンドを使用して展開できます。 このコマンドには、プレイブックファイルへの相対パスという1つの必須引数しかありません。



アンシブルプレイブック



プレイブックファイルは、実行中のタスクまたは他のプレイブックのリストです。 ネストのおかげで、レイヤー間でタスクを効果的に分離し、現在必要なものだけを実行する機能を実現できます。

展開の例として、 myawesomestartup取り上げます。これは、 多数の旅客5スタンドアロンとWebサーバーとしてのnginxとバックグラウンドタスク用のsidekiqを備えた一種のRailsアプリケーションです。 この例の物理インフラストラクチャは、2つの運用サーバーです。

 prima.myawesomestartup.com secunda.myawesomestartup.com
      
      





そして、1つのステージング:

 plebius.myawesomestartup.com
      
      





ansibleフォルダーで、他のすべてのプレイブックを含むdeploy.ymlマスタープレイブックを定義し、

 --- - hosts: hosts - include: release.yml #    - include: app.yml #   - - include: sidekiq.yml #   sidekiq
      
      





ansible-playbook deploy.ymlコマンドを使用して、デプロイ全体を実行します。 ただし、新しいリリースを展開せずにアプリケーションを再起動する必要がある場合は、プレイブックを個別に実行できます。

hosts変数に注意してください;展開が実行されるサーバーに関する情報が含まれています。 この変数は、グローバルなansible構成で定義できますが、インベントリファイルを使用して別の方法で行います。



インベントリファイルとアプリケーション構成



Ansibleは、ホストグループ、その階層、および設定を保存するためのインベントリファイルを提供します。 これらは、非常に単純な構文を持つiniファイルです。



ホストのグループを説明できます。

 [hosts:children] prima secunda
      
      





グループで、ホスト自体を宣言します。

 [prima] prima.myawesomestartup.com [secunda] secunda.myawesomestartup.com
      
      





特定の各ホストに固有の変数を宣言します。

 [prima:vars] ansible_env_name=production rails_env_name=production database_name={{ lookup('env', 'PRIMA_DB_NAME') }} database_username={{ lookup('env', 'PRIMA_DB_LOGIN') }} database_password={{ lookup('env', 'PRIMA_DB_PASSWORD') }} database_host={{ lookup('env', 'PRIMA_DB_HOST') }} database_port={{ lookup('env', 'PRIMA_DB_PORT') }}
      
      





中括弧に注意してください-ansibleでは、すべてのファイルはJinja2テンプレートです。 この例では、環境変数は、テンプレートエンジンと展開が実行されるマシンからのルックアップコマンドを介して補間されます。 これは、秘密鍵やデータベース接続文字列などの機密情報をバージョン管理システムに保存しないために役立ちます。



この例が機能するためには、 〜/ .bashrcまたは〜/ .zshrcで次の変数を宣言するか(より安全で便利ではありません)、各デプロイメントの前に毎回それらをエクスポートする必要があります。

 export PRIMA_DB_NAME=myawesomestartup_production export PRIMA_DB_LOGIN=myawesomestartup export PRIMA_DB_PASSWORD=secret export PRIMA_DB_HOST=db.myawesomestartup.com export PRIMA_DB_PORT=3306
      
      





完全なインベントリ/プロダクションおよびインベントリ/ステージングファイルは次のとおりです。

在庫/生産
 ; production [prima] prima.myawesomestartup.com [prima:vars] ansible_env_name=production rails_env_name=production database_name={{ lookup('env', 'PRIMA_DB_NAME') }} database_username={{ lookup('env', 'PRIMA_DB_LOGIN') }} database_password={{ lookup('env', 'PRIMA_DB_PASSWORD') }} database_host{{ lookup('env', 'PRIMA_DB_HOST') }} database_port={{ lookup('env', 'PRIMA_DB_PORT') }} git_branch=master app_path=/srv/www/prima.myawesomestartup.com custom_server_options=--no-friendly-error-pages sidekiq_process_number=4 [secunda] secunda.myawesomestartup.com [secunda:vars] ansible_env_name=production rails_env_name=production database_name={{ lookup('env', 'SECUNDA_DB_NAME') }} database_username={{ lookup('env', 'SECUNDA_DB_LOGIN') }} database_password={{ lookup('env', 'SECUNDA_DB_PASSWORD') }} database_host={{ lookup('env', 'SECUNDA_DB_HOST') }} database_port={{ lookup('env', 'SECUNDA_DB_PORT') }} git_branch=master app_path=/srv/www/secunda.myawesomestartup.com custom_server_options=--no-friendly-error-pages sidekiq_process_number=4 [hosts:children] prima secunda
      
      







インベントリ/ステージング
 ; staging [plebius] plebius.myawesomestartup.com [plebius:vars] ansible_env_name=staging rails_env_name=production database_name={{ lookup('env', 'PLEBIUS_DB_NAME') }} database_username={{ lookup('env', 'PLEBIUS_DB_LOGIN') }} database_password={{ lookup('env', 'PLEBIUS_DB_PASSWORD') }} database_host={{ lookup('env', 'PLEBIUS_DB_HOST') }} database_port={{ lookup('env', 'PLEBIUS_DB_PORT') }} git_branch=develop app_path=/srv/www/plebius.myawesomestartup.com custom_server_options=--friendly-error-pages sidekiq_process_number=4 [hosts:children] plebius
      
      







設定テンプレートをansible / configsフォルダーに配置します。

configs / database.yml
 # configs/database.yml {{rails_env_name}}: adapter: mysql2 database: {{database_name}} username: {{database_username}} password: {{database_password}} host: {{database_host}} port: {{database_port}} secure_auth: false
      
      







バージョン管理システムに安全に保存できる設定については、 dotenvを好みます

ansible /環境フォルダーに次のファイル構造を作成します。

 production/ prima.env secunda.env staging/ plebius.env
      
      







カピストラーノでのリリース



Capistranoはデフォルトで、サーバー上でかなりよく考え抜かれたファイル構造を提供します。

 releases/ 20150631130156/ 20150631130233/ 20150631172431/ 20150704162516/ 20150712165952/ current - -> /www/domain/releases/20150712165952/ shared/
      
      





releasesフォルダーには、このリリースのデプロイメント時間のタイムスタンプが含まれる20150812165952という形式の名前のフォルダーに、最新の5つの最新リリースが含まれています。 各リリース内には、リリースが作成されたコミットのハッシュを含むREVISIONファイルがあります。

Simlink currentは、 releasesフォルダー内の最新リリースを指します。

共有フォルダーには、すべてのリリースに共通のファイル( .pid.sockなど )と、バージョン管理システムから除外されているファイル( database.ymlなど )が含まれています。 これらすべてにより、デプロイメントの失敗や予期しないバグを伴うコードのロールアウトが発生した場合に、アプリケーションを安全にロールバックできます。

Ansibleでこれを繰り返します。

ansible / release.yml
 # ansible/release.yml --- - hosts: hosts #    inventory-    tasks: #     app_path  shared_path    .    - include: tasks/_set_vars.yml tags=always #        - set_fact: timestamp="{{ lookup('pipe', 'date +%Y%m%d%H%M%S') }}" - set_fact: release_path="{{ app_path }}/releases/{{ timestamp }}" #    .    ansible   - name: Ensure shared directory exists file: path={{ shared_path }} state=directory - name: Ensure shared/assets directory exists file: path={{ shared_path }}/assets state=directory - name: Ensure tmp directory exists file: path={{ shared_path }}/tmp state=directory - name: Ensure log directory exists file: path={{ shared_path }}/log state=directory - name: Ensure bundle directory exists file: path={{ shared_path }}/bundle state=directory #       - name: Leave only last releases shell: "cd {{ app_path }}/releases && find ./ -maxdepth 1 | grep -G .............. | sort -r | tail -n +{{ keep_releases }} | xargs rm -rf" - name: Create release directory file: path={{ release_path }} state=directory #       - name: Checkout git repo into release directory git: repo={{ git_repo }} dest={{ release_path }} version={{ git_branch }} accept_hostkey=yes #       REVISION    - name: Get git branch head hash shell: "cd {{ release_path }} && git rev-parse --short HEAD" register: git_head_hash - name: Create REVISION file in the release path copy: content="{{ git_head_hash.stdout }}" dest={{ release_path }}/REVISION #     rails  - name: Set assets link file: src={{ shared_path }}/assets path={{ release_path }}/public/assets state=link - name: Set tmp link file: src={{ shared_path }}/tmp path={{ release_path }}/tmp state=link - name: Set log link file: src={{ shared_path }}/log path={{ release_path }}/log state=link #   .env  database.yml   .          . - name: Copy .env file template: src=environments/{{ansible_env_name}}/{{ansible_hostname}}.env dest={{ release_path }}/.env - name: Copy database.yml template: src=configs/database.yml dest={{ release_path }}/config - set_fact: rvm_wrapper_command="cd {{ release_path }} && RAILS_ENV={{ rails_env_name }} rvm ruby-{{ ruby_version }}@{{ full_gemset_name }} --create do" # Bundle, ,  ... - name: Run bundle install shell: "{{ rvm_wrapper_command }} bundle install --path {{ shared_path }}/bundle --deployment --without development test" - name: Run db:migrate shell: "{{ rvm_wrapper_command }} rake db:migrate" - name: Precompile assets shell: "{{ rvm_wrapper_command }} rake assets:precompile" #      current - name: Update app version file: src={{ release_path }} path={{ app_path }}/current state=link
      
      







これらの変数はすべてのプレイブックとサーバーで同一であるため、いくつかの変数は別のミックスインタスクで設定されました。

 # ansible/tasks/_set_vars.yml --- - set_fact: app_name="myawesomestartup" - set_fact: ruby_version="2.2.2" - set_fact: ruby_gemset="myawesomestartup" - set_fact: git_repo="ilpagency/rails-sidekiq-ansible-sample" - set_fact: keep_releases="5" - set_fact: full_app_name="{{ app_name }}-{{ ansible_env_name }}" - set_fact: full_gemset_name="{{ ruby_gemset }}-{{ ansible_env_name }}" - set_fact: current_path="{{ app_path }}/current" - set_fact: shared_path="{{ app_path }}/shared"
      
      







乗客とsidekiqの実行-Ansibleタグとループ



ansible / app.ymlアプリケーションの状態を管理するための別のプレイブックを作成します。これを使用して、アプリケーションを起動、停止、または再起動できます。 他のプレイブックと同様に、個別に実行することも、マスタープレイブックの一部として実行することもできます。

柔軟性を高めるには、 タグ app_stopおよびapp_startを追加します 。 タグを使用すると、展開中に明示的に指定されたタスクの部分のみを実行できます。 展開中にタグを指定しない場合、プレイブック全体が実行されます。



実際には次のようになります。

 #  : ansible-playbook app.yml -i inventory/production #  : ansible-playbook app.yml -i inventory/production -t "app_stop" #  : ansible-playbook app.yml -i inventory/production -t "app_start" #   : ansible-playbook app.yml -i inventory/production -t "app_stop,app_start"
      
      





そして、これが実装です:

ansible / app.yml
 # ansible/app.yml --- - hosts: hosts #    inventory-    tasks: - include: tasks/_set_vars.yml tags=always # always   ,      ,       - set_fact: socks_path={{ shared_path }}/tmp/socks tags: always - name: Ensure sockets directory exists file: path={{ socks_path }} state=directory tags: always - set_fact: app_sock={{ socks_path }}/app.sock tags: always - set_fact: pids_path={{ shared_path }}/tmp/pids tags: always - name: Ensure pids directory exists file: path={{ pids_path }} state=directory tags: always - set_fact: app_pid={{ pids_path }}/passenger.pid tags: always - set_fact: rvm_wrapper_command="cd {{ current_path }} && RAILS_ENV={{ rails_env_name }} rvm ruby-{{ ruby_version }}@{{ full_gemset_name }} --create do" tags: always - include: tasks/app_stop.yml tags=app_stop #             app_start - include: tasks/app_start.yml tags=app_start #   ,   - app_stop
      
      







アプリケーションを開始および停止するタスクは、 ansible / tasks / app_start.ymlファイルとansible / tasks / app_stop.ymlファイルで個別に強調表示されます。

ansible / tasks / app_start.yml
 # ansible/tasks/app_start.yml --- - name: start passenger shell: "{{ rvm_wrapper_command }} bundle exec passenger start -d -S {{ app_sock }} --environment {{ rails_env_name }} --pid-file {{ app_pid }} {{ custom_server_options }}"
      
      







ansible / tasks / app_stop.yml
 # ansible/tasks/app_stop.yml --- - name: stop passenger shell: "{{ rvm_wrapper_command }} bundle exec passenger stop --pid-file {{ app_pid }}" ignore_errors: yes #     ...  .  -   .
      
      







sidekiqの場合、状況は似ています。 そのために、対応するsidekiq_stopおよびsidekiq_startタグをサポートする別のプレイブックansible / sidekiq.ymlを実装します

ansible / app.yml
 # ansible/sidekiq.yml --- - hosts: hosts tasks: - include: tasks/_set_vars.yml tags=always - set_fact: pids_path={{ shared_path }}/tmp/pids tags: always - name: Ensure pids directory exists file: path={{ pids_path }} state=directory tags: always - set_fact: rvm_wrapper_command="cd {{ current_path }} && RAILS_ENV={{ rails_env_name }} rvm ruby-{{ ruby_version }}@{{ full_gemset_name }} --create do" tags: always - include: tasks/sidekiq_stop.yml tags=sidekiq_stop - include: tasks/sidekiq_start.yml tags=sidekiq_start
      
      







開始タスクと停止タスクは、 ansible /tasks/sidekiq_start.ymlファイルとansible /tasks/sidekiq_stop.ymlファイルにも個別に割り当てられます。 sidekiqを実際に開始および停止することに加えて、これらのタスクはAnsible でループを操作する方法を示し、複数のプロセスを一度に開始/停止する問題を解決します

ansible / tasks / sidekiq_start.yml
 # ansible/tasks/sidekiq_start.yml --- - name: start sidekiq shell: "{{ rvm_wrapper_command }} bundle exec sidekiq --index {{ item }} --pidfile {{ pids_path }}/sidekiq-{{ item }}.pid --environment {{ rails_env_name }} --logfile {{ shared_path }}/log/sidekiq.log --daemon" #  item -  i  .   with_sequence  4,  item  1,2,3,4 with_sequence: count={{ sidekiq_process_number }} #   sidekiq          
      
      







ansible / tasks / sidekiq_stop.yml
 # ansible/tasks/sidekiq_stop.yml --- - name: stop sidekiq shell: "{{ rvm_wrapper_command }} bundle exec sidekiqctl stop {{ pids_path }}/sidekiq-{{ item }}.pid 20" ignore_errors: yes #  ,     ,   ,    . with_sequence: count={{ sidekiq_process_number }}
      
      









おわりに



これで、Ansibleを使用してRailsアプリケーションをデプロイできます。

 cd myawesomestartup/ansible # : ansible-playbook deploy.yml -i inventory/production #  : ansible-playbook app.yml -i inventory/production #  sidekiq: ansible-playbook sidekiq.yml -i inventory/production #      : ansible-playbook deploy.yml -i inventory/staging -e git_branch="hotfix/14082015-777-production_bug"
      
      





この記事では例のみを示しているため(実際には機能しますが)、さらに先へ進む方法に注意します。

  1. Passengerのグレースフルリスタートを実装します。
  2. ネストされたプレイブックの代わりにAnsible ロールメカニズムを使用します。
  3. ロールバックを実装して、以前のリリースにロールバックします。
  4. そして一般に、この例を開発者の推奨事項にさらに準拠させます。


そして最も重要なことです。 Ansibleは、アプリケーションのリリースを展開してサーバーを再起動するだけではありません。 繰り返しますが、ansibleは単なる展開ツールではなく、完全な構成管理ツールです。 たとえば、ロールの助けを借りて、アプリケーションの展開をゼロから直接、ベアサーバーハードウェアに構成できます。 また、yml表記のシンプルさにより、見つかったソリューションをニーズに合わせて簡単に変更できます。



この記事のすべてのソースコードはGitHub入手できます 。 ご清聴ありがとうございました。




All Articles