デベロッパーのkyosimotoです。
Ansibleをバージョンアップ作業の自動化ツールとして導入するための手順、おすすめ構成などについて紹介させていただきます。
目次
なぜAnsible
私は担当サービスのバージョンアップ作業を自動化するため、シェルベースのスクリプトを開発・運用してきました。 スクリプト導入により、ほとんどの作業は自動化され、運用コストは大幅に削減されました。
半面、バージョンアップスクリプトの属人化が課題となっており、リリース時のトラブルが起きた場合に、作業担当者による 原因調査と復旧作業が難しい状態となっています。
シェルスクリプトで作成したバージョンアップスクリプトには、フレームワークのような拘束力が無く、長い運用の中でイレギュラーケースに対応したコードやフラグが追加され、複雑化の道をすすみ続ける可能性があります。
さらには、今のところこの複雑化したスクリプトをポジティブに学習したいという人はいません。問題が起きたとしても、開発担当者に聞けばすぐに解決できるからです。
私は属人化を解消するためには、シェルスクリプトによる実装をやめ、学習コストが低く複雑なコードを生まない自動化ツールの導入が必要と考えており、この要件にマッチしたAnsibleを導入提案することになりました。
どんな感じ?
今のところメリットと感じているのは下記3点です。
学習コストが低い
設定ファイルはYAMLという形式で記述します。設定ファイルがシンプルで、初めての人でも内容をすぐに理解できると思います。
運用作業用のモジュールが充実していますので、プログラミング書くことも読むこともほとんどありません。導入コストが低い
管理対象サーバには余計なツールやデーモンをインストールする必要がありません。
SSHとPythonさえ使えれば、Ansibleからの操作が可能ですので、運用チームへも提案しやすいと思います。運用コストが低い
少しの工夫で設定ファイルをそのまま手順書として扱うことができます。
Ansibleの基本
実行方法
Ansibleの実行コマンドは以下の通りです。
$ ansible-playbook -i {Inventory} {Playbook}
Inventoryには、サーバ名やIPアドレスなどの管理対象ノードの情報、Playbookには管理対象ノードで実行するタスクを記述します。
実行イメージ
Ansibleは、上記コマンドを実行するとPlaybookの内容をPythonのプログラムに変換します。
変換されたプログラムファイルは、Inventory(インベントリ)に記述された管理対象ノードに転送後に実行されます。
マシン要件
対象 | 要件 |
---|---|
コントロールマシン | ・Python 2 (version 2.6 or 2.7)、またはPython3(version 3.5以上)がインストールされている。 ・Windowsはサポート対象外。 (詳細) |
管理対象ノード | ・SSH接続できる。 ・Python 2.6以上がインストールされている。 (詳細) |
ファイル構成
私のお勧めするファイル構成サンプルを紹介します。
ディレクトリ構成(サンプル)
myapp_verup product.ini # inventory (本番環境のホスト名/IPを記述) staging.ini # inventory (ステージングのホスト名/IPを記述) development.ini # inventory (社内検証環境のホスト名/IPを記述) versionup.yml # playbook (バージョンアップ手順を記述) roles/ # 具体的バージョンアップ手順を実装するディレクトリ apacheを停止する/ apacheを起動する/ apacheをバージョンアップする/ cronを停止する/ cronを起動する/ postgresqlを停止する/ postgresqlを起動する/ postgresqlをバージョンアップする/ アプリケーションをバージョンアップする/
playbook(サンプル)
以下、playbookファイルの内容です。
- myapp_verup/versionup.yml
--- - hosts: all roles: - cronを停止する - hosts: webservers roles: - apacheを停止する - apacheをバージョンアップする - phpをバージョンアップする - アプリケーションをバージョンアップする - hosts: dbservers roles: - postgresqlを停止する - postgresqlをバージョンアップする - postgresqlを起動する。 - hosts: webservers roles: - apacheを起動する - hosts: all roles: - cronを起動する
ファイル構成のポイント
ファイル構成を考える上で、お勧めするポイントは下記2点です。
playbookを日本語で記述する playbookのタスクを日本語化することで、設定ファイルの可読性が上がる、playbookがそのまま手順書/ドキュメントになるという点でメリットが大きいと考えています。
バージョンアップ作業に集中する。
バージョンアップ作業以外のタスクを含めないようにします。
例えば、サーバ構成管理や冪等性のための実装を行うと、Ansibleの設定は複雑化します。 複雑化は属人化を進行させますし、(Serverspecなど)ツールを使ったテストなども検討する必要がでてくるでしょう。
検証環境の準備
ここからは、Ansibleの検証用環境の構築手順について記載します。
検証環境の説明
仮想マシンの構築に Vagrant + Virtualbox を利用します。 検証用に構築するサーバは以下の通りです。
ホスト名 | IPアドレス | OS | MW/Tool |
---|---|---|---|
control | 192.168.33.100 | CentOS 6.9 | Ansible |
web | 192.168.33.101 | CentOS 6.9 | Apache2.2 + PHP7.1 |
db | 192.168.33.102 | CentOS 6.9 | PostgreSQL9.6 |
別のディストリビューションで検証したい場合は、Vagrant Cloudよりboxイメージを検索し、後述する「vagrant init」コマンドの引数に指定してください。
検証用仮想マシンの構築手順
# Vagrantの作業用ディレクトリを作成します。 $ cd $ mkdir -p vagrant_work/ansible_test $ cd vagrant_work/ansible_test # Vagrantの作業用ディレクトリを初期化します。 $ vagrant init bento/centos-6.9 # 出力されたVagrantfileをエディタで修正します。 $ vi Vagrantfile ----- # config.vm.box = "bento/centos-6.9" # この行をコメントアウトし、以下の設定をコピペする。 config.vm.define "control" do |node| node.vm.box = "bento/centos-6.9" node.vm.hostname = "control" node.vm.network :private_network, ip: "192.168.33.100" end config.vm.define "web" do |node| node.vm.box = "bento/centos-6.9" node.vm.hostname = "web" node.vm.network :private_network, ip: "192.168.33.101" end config.vm.define "db" do |node| node.vm.box = "bento/centos-6.9" node.vm.hostname = "db" node.vm.network :private_network, ip: "192.168.33.102" end ----- # 仮想サーバを起動します $ vagrant up # 仮装サーバが起動するまでしばらく待ちます。
仮想サーバにSSH接続する
$ vagrant ssh control
# パスワードは「vagrant」
Windowsの場合は、ターミナルソフトで接続します。
ホスト | 192.168.33.100 |
---|---|
user | vagrant |
password | vagrant |
Ansible実行環境の構築
AnsibleのインストールとSSH設定の手順について記載します。
Ansibleのインストール
# Vagrantの作業ディレクトリより仮想サーバにSSH接続します。 $ vagrant ssh control # パスワードは「vagrant」 # EPELリポジトリを追加 $ sudo yum install -y epel-release # Ansibleのインストール $ sudo yum install -y ansible # Ansibleのバージョン確認 $ ansible --version ansible 2.3.2.0 config file = /etc/ansible/ansible.cfg configured module search path = Default w/o overrides python version = 2.6.6 (r266:84292, Aug 18 2016, 15:13:37) [GCC 4.4.7 20120313 (Red Hat 4.4.7-17)]
SSH接続設定
接続先サーバへのSSH接続を簡単にするためSSH設定を記述します。
$ vi ~/.ssh/config ------------ Host * StrictHostKeyChecking no UserKnownHostsFile=/dev/null Host web HostName 192.168.33.101 User vagrant Host db HostName 192.168.33.102 User vagrant ------------ # 設定ファイルのパーミッションを変更します。 $ vi ~/.ssh/config ------------ Host * StrictHostKeyChecking no UserKnownHostsFile=/dev/null Host web HostName 192.168.33.101 User vagrant Host db HostName 192.168.33.102 User vagrant ------------ # 設定ファイルのパーミッションを変更します。 $ chmod 600 ~/.ssh/config # SSHの公開鍵を登録します。 $ ssh-keygen -t rsa Generating public/private rsa key pair. Enter file in which to save the key (/home/vagrant/.ssh/id_rsa): # Enterキー入力 Enter passphrase (empty for no passphrase): # Enterキー入力 Enter same passphrase again: # Enterキー入力 Your identification has been saved in /home/vagrant/.ssh/id_rsa. Your public key has been saved in /home/vagrant/.ssh/id_rsa.pub. The key fingerprint is: # 秘密鍵と公開鍵が作成されます。 ~/.ssh/id_rsa ~/.ssh/id_rsa.pujb # 作成したSSH公開鍵を接続先サーバにコピーします。 $ ssh-copy-id web # パスワードは「vagrant」 $ ssh-copy-id db # パスワードは「vagrant」 # 接続先サーバにパスワードなしでアクセスできることを確認します。 $ ssh web $ hostname web $ exit $ ssh db $ hostname db $ exit
検証用仮想マシンのミドルウェアセットアップ
バージョンアップ手順の検証用に、予めミドルウェアをセットアップします。
WEBサーバの構築 (Apache2.2 + PHP7.1)
# WEBサーバにSSH接続 $ ssh web # Apacheをインストール $ sudo yum install -y httpd # PHPをインストール $ sudo yum install -y epel-release $ sudo rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm $ sudo yum -y --enablerepo=remi-php71,epel install php php-cli php-common php-mbstring php-mcrypt php-pdo php-xml php-json php-devel php-pecl-zip php-pgsql $ sudo service httpd restart $ sudo chkconfig httpd on # テストページを作成してApache+PHPの連携確認 $ sudo vi /var/www/html/phpinfo.php -------------- <?php phpinfo(); -------------- # ブラウザから http://192.168.33.101/phpinfo.php にアクセスできることを確認 # SSH接続を終了 $ exit
DBサーバの構築(PostgreSQL9.6)
# DBサーバにSSH接続 $ ssh db # PostgreSQLのインストール $ sudo yum install -y https://yum.postgresql.org/9.6/redhat/rhel-6.9-x86_64/pgdg-redhat96-9.6-3.noarch.rpm $ sudo yum -y install postgresql96-server # PostgreSQLの初期設置 $ sudo service postgresql-9.6 initdb $ sudo vi /var/lib/pgsql/9.6/data/pg_hba.conf -------------- # 末尾に追加 host all all 192.168.33.101/32 trust -------------- $ sudo vi /var/lib/pgsql/9.6/data/postgresql.conf # 接続設定追加(59行目あたり) -------------- #listen_addresses = 'localhost' listen_addresses = '*' -------------- # PostgreSQLの再起動 $ sudo service postgresql-9.6 start $ sudo chkconfig postgresql-9.6 on # データベース&テーブル作成 $ sudo su - postgres -c "psql" # ココからはSQLモード > \c test > create database test; > create table t_staff (id int, name text); > insert into t_staff values (1, 'あああああ'), (2, 'いいいいい'); > \q # SSH接続を終了 $ exit
WEB/DBサーバの連携チェック
# WEBサーバにSSH接続 $ ssh web # WEBサーバとDBサーバ間の疎通確認 $ sudo vi /var/www/html/test.php --------- <?php $connectString = "host=192.168.33.102 port=5432 dbname=test user=postgres"; $conn = pg_connect($connectString); $result = pg_query($conn, "select * from t_staff"); var_dump(pg_fetch_all($result)); --------- # ブラウザから下記URLにアクセスして、DBレコードが出力されることを確認 http://192.168.33.101/test.php
プロジェクトディレクトリ作成
ファイル構成の項目で紹介したAnsibleプロジェクトを作成していきましょう。
バージョンアッププロジェクト用のディレクトリ作成
# HOMEディレクトリにプロジェクトディレクトリを作成します。 $ mkdir ~/myapp_verup # フォルダを作成します $ cd ~/myapp_verup $ mkdir -p roles/apacheを停止する/tasks $ mkdir -p roles/apacheを起動する/tasks $ mkdir -p roles/apacheをバージョンアップする/tasks $ mkdir -p roles/cronを停止する/tasks $ mkdir -p roles/cronを起動する/tasks $ mkdir -p roles/postgresqlを停止する/tasks $ mkdir -p roles/postgresqlを起動する/tasks $ mkdir -p roles/postgresqlをバージョンアップする/tasks $ mkdir -p roles/アプリケーションをバージョンアップする/tasks
Inventoryの作成
Inventoryでは、管理対象ノードのホスト名、またはIPアドレスとグループの定義を行います。 ファイルはINIファイル形式で記述します。
- myapp_verup/development.ini
[webservers] 192.168.33.101 [dbservers] 192.168.33.102
Inventoryの作成(補足)
Inventoryを本番環境用、ステージング環境用、開発環境用に分けて管理することで バージョンアップ対象の切り替えできるようにします。
以下サンプルです。
- myapp_verup/product.ini (本番環境用)
# 本番環境用のInventory
[webservers]
192.168.34.101
192.168.34.102
192.168.34.103
[dbservers]
192.168.34.104
192.168.34.105
- myapp_verup/staging.ini (ステージング環境用)
# ステージング環境用のInventory # セクション名(グループ名)は同じでIPアドレスのみ異なる。 [webservers] 192.168.35.101 192.168.35.102 192.168.35.103 [dbservers] 192.168.35.104 192.168.35.105
Playbookの作成
Playbookには、バージョンアップ手順を記述します。
(どのサーバでどんなタスクをどのような順番で実行するかを記述します)
- myapp_verup/versionup.yml
--- - hosts: all roles: - cronを停止する - hosts: webservers roles: - apacheを停止する - apacheをバージョンアップする - phpをバージョンアップする - アプリケーションをバージョンアップする - hosts: dbservers roles: - postgresqlを停止する - postgresqlをバージョンアップする - postgresqlを起動する - hosts: webservers roles: - apacheを起動する - hosts: all roles: - cronを起動する
ファイルはYAML形式となりますので、拡張子は「yml」、1行目は「---」としてください。
2行目以降にバージョンアップ手順を記述します。書き方のルールは以下の通りです。
セクション | 解説 |
---|---|
hosts | inventoryに定義したグループ名を記述します。 "all"の場合、inventoryに記述した全サーバを対象に処理を実行します。 |
roles | hostsセクションに指定したサーバで実行するタスクを記載します。 タスクは「◯◯を停止する」「◯◯を起動する」「◯◯をバージョンアップする」くらいの粒度で記述しておき、具体的な処理内容をroles配下のタスク名と同名のディレクトリ配下に実装します。 |
ansible.cfgの作成
Ansibleの動作設定を記述するファイルで、INI形式で記述します。
- myapp_verup/ansible.cfg
[defaults] # 実行時のログを出力するファイルを指定します。 log_path=/tmp/ansible.log [privilege_escalation] # タスクの実行ユーザをrootに設定します become = true become_user = root
Roleの作成
Playbookのrolesディレクティブに記述したタスクの実態をrolesディレクトリの配下作成します。 例えば、「Cronを起動する」というタスクは、roles/Cronを起動する/tasks/main.yml に、処理内容を記述します。
ミドルウェアの起動
service コマンドが準備されているミドルウェア(デーモン)であれば、「service」モジュールを使って以下のように記述します。
- myapp_verup/roles/cronを起動する/tasks/main.yml
--- - name: crondを起動する service: name: crond state: started
- myapp_verup/roles/apacheを起動する/tasks/main.yml
--- - name: Apacheを起動する service: name: httpd state: started
- myapp_verup/roles/postgresqlを起動する/tasks/main.yml
--- - name: PostgreSQLを起動する service: name: postgresql-9.6 state: started
※「service」モジュールの使い方はこちら
(補足)ミドルウェアの起動
service コマンドが提供されていないミドルウェアの場合は、「shell」モジュールと「wait_for」モジュールで実装することもできます。
--- - name: 起動コマンドを実行する shell: /usr/local/myapp/apache/bin/apachectl start - name: 80番ポートの疎通確認が終わるまで待機する wait_for: host: localhost port: 80 state: started delay: 1 timeout: 60
shell モジュールは終了ステータスコードが0以外は、すべてエラーとして処理を中断しますので注意が必要です。 ステータスコードが0以外でも処理を継続する場合には、下記サンプルを参考にしてください。
--- - name: スクリプトを実行する shell: /usr/local/myapp/bin/hoge.sh register: exitStatus failed_when: exitStatus.rc not in [0, 100] # 終了ステータスが0 or 100の場合はエラーにしない
※「shell」モジュールの使い方はこちら
※「wait_for」モジュールの使い方はこちら
ミドルウェアの停止
service コマンドが準備されているミドルウェア(デーモン)であれば、「service」モジュールを使って以下のように記述します。
- myapp_verup/roles/cronを停止する/tasks/main.yml
--- - name: crondを停止する service: name: crond state: stopped
- myapp_verup/roles/apacheを停止する/tasks/main.yml
--- - name: Apacheを停止する service: name: httpd state: stopped
- myapp_verup/roles/postgresqlを停止する/tasks/main.yml
--- - name: PostgreSQLを停止する service: name: postgresql-9.6 state: stopped
ミドルウェアのバージョンアップ
RPMがyumコマンドでバージョンアップできる場合は、「yum」モジュールを利用して「latest」の状態に更新します。
- myapp_verup/roles/apacheをバージョンアップする/tasks/main.yml
--- - name: RPMを更新する yum: name: httpd state: latest
- myapp_verup/roles/phpをバージョンアップする/tasks/main.yml
--- - name: RPMを更新する yum: name={{ item }} state=latest enablerepo=remi,epel with_items: - php - php-cli - php-common - php-mbstring - php-mcrypt - php-pdo - php-xml - php-json - php-devel - php-pecl-zip - php-pgsql
- myapp_verup/roles/postgresqlをバージョンアップする/tasks/main.yml
--- - name: RPMを更新する yum: name=postgresql96-server state=latest
リポジトリ管理されていない(カスタムRPMをつかっている)場合は、以下設定を参考にしてください。
myapp_verup └ roles └ Apacheをバージョンアップする ├ tasks │ └ main.yml ├ files │ └ myapp_apache2.2.99.rpm # カスタムPRMを格納 ├ templates │ └ httpd.conf.j2 # テンプレートファイルを格納。拡張子は「.j2」にする。(テンプレートエンジン「Jinja2」を利用) └ vars └ main.yml # RPMのファイル名やチェックサム値などを記述する
※ディレクトリ構成については公式ページのBest Practices の「Directory Layout」を参考にしています。
- myapp_verup/roles/Apacheをバージョンアップする/tasks/main.yml
--- - name: RPMファイルを転送する copy: src=files/{{ rpm_file_name }} dest=/tmp - name: RPMファイルの状態を取得する stat: path: /tmp/{{ rpm_file_name }} register: file_status - name: チェックサム値を確認する fail: msg='MD5 value did not match' when: file_status.stat.md5 != rpm_file_md5 - name: "RPMを更新する ({{ rpm_file_name }})" shell: rpm -Uvh --force --nodeps /tmp/{{ rpm_file_name }} args: chdir: "/tmp" register: verup_result - name: ステータスコードを確認する fail: msg="Failed upgrade rpm." when: verup_result.rc != 0 - name: httpd.confを差し替える template: src=httpd.conf.j2 dest=/usr/local/vanguard/apache/conf/httpd.conf owner=root group=root mode=644
※「copy」モジュールの使い方はこちら
※「stat」モジュールの使い方はこちら
※「fail」モジュールの使い方はこちら
※「template」モジュールの使い方はこちら
...中略...
# サーバ毎に異なる設定は 2重波括弧と変数名を記述しておきます。
ServerName {{ apache_server_name }}:80
...中略...
※ 上記変数部分は、例えばInventoryファイルに記述した変数の値に自動で 置き換えることができます。
...中略... [web:vars] apache_server_name = myapp.example.com ...中略...
- myapp_verup/roles/Apacheをバージョンアップする/vars/main.yml
--- # RPMファイル名を記述します。 # この値はtask/main.ymlで参照されます。 rpm_file_name: "vg_httpd-2.4.25-centos6.x86_64.rpm" # RPMファイルのチェックサム値を記述します。 # この値はtask/main.ymlで参照されます。 rpm_file_md5: "4f8009b1cbcf5dbc7f082773d2f0d661"
playbookの実行
$ cd ~/myapp_verup $ ansible-playbook -i development.ini versionup.yml
実行結果は以下の通りです。
日本語で書いたタスクがそのままターミナル上のログに出力されていることが確認できます。
(/tmp/ansible.log にも出力されます。)
最後に
本記事は、運用チーム向けにAnsibleを使ったバージョンアップ自動化提案後に書いた記事です。
本番運用が開始されれば、いろいろと課題はでてくると思いますので、知見がたまりましたら
改めて情報を共有させていただこうと思います。
以上、ありがとうございました。
エンジニア中途採用サイト
ラクスでは、エンジニア・デザイナーの中途採用を積極的に行っております!
ご興味ありましたら是非ご確認をお願いします。
https://career-recruit.rakus.co.jp/career_engineer/カジュアル面談お申込みフォーム
どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。
以下フォームよりお申込みください。
forms.gleイベント情報
会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! rakus.connpass.com