中途三年目、堀越です。
Ansible で GitLab CI/CD 上にデプロイジョブを実装しましたのでそのアウトプットです。
はじめに
もともとSSHしつつポチポチとシェルを叩いて Play Framework をデプロイしていたのですが、自動化しましょうという話を開発チームでしました。
Ansible とか使ったら便利そうという意見があり、以前から関心があったということで便乗して担当させていただく運びとなった次第です。
旧デプロイ環境
踏み台サーバーにシェルスクリプトが配置してあり、叩いてみると下記の処理が一連して実行される仕組みになっていました。WEBサーバーは production と development の2つの環境が存在していました。*1
踏み台サーバーにて
- コード最新化
- git fetch, checkout, pull などする*2
- パッケージ
- sbt clean dist
- SCPしてファイル転送
- ZIPファイルを Host に転送
Host にて
- ZIPファイル解凍
- Javaプロセス kill → run
図で書くと下記のような感じです。
新デプロイ環境
大きくやりたいことは変わりませんが多段SSHという仕組みを使って、デプロイ関連のオペレーションを GitLab Runner から実行できるようにします。
図で書くと下記のような感じです。
Ansible の実装
構成
プロジェクトのルートに ansible
ディレクトリをきって、下記のような構成としました。
$ cd ansible
$ tree
.
├── ansible.cfg
├── deploy.yml
├── dev
├── group_vars
│ ├── dev.yml
│ └──prod.yml
└── prod
Invetory File
WEBサーバーの実行環境になる Host とログインユーザーを定義します。グループ名は環境毎に指定します。*3
dev
[dev] 36.23.54.123 # host's ip address [all:vars] ansible_ssh_user=ansible
prod
[prod] 36.23.54.124 # host's ip address [all:vars] ansible_ssh_user=ansible
Variables
group_vars を利用して多段SSHする際のプロキシコマンドを設定します。*4
group_vars/dev.yml
ansible_ssh_common_args: '-o ProxyCommand="ssh -W %h:%p -q ansible@53.43.34.68"'
group_vars/prod.yml
ansible_ssh_common_args: '-o ProxyCommand="ssh -W %h:%p -q ansible@53.43.34.68"'
Playbook
デプロイの詳細なタスクを定義します。
- hosts: all become: yes become_method: sudo tasks: - name: copy source file to remote host and unpacks an archive. unarchive: src: ../target/universal/sample-1.0-SNAPSHOT.zip dest: /home/ansible/ - name: kill java proccess shell: pkill java ignore_errors: True - name: start java process shell: "nohup sample-1.0-SNAPSHOT/bin/sample -Dhttp.port=80 -Dplay.http.secret.key='{{ secretKey }}' &"
下記より、詳細に見ていきます。
hosts
dev, prod で共通の Playbook なので all
を指定します。
become, become_user
今回は強い権限で実行したかったので become_method
には sudo
を指定しています。
tasks/unarchive
ファイル転送とZIPファイルの解凍は unarchive
モジュールで一括で行えるので色々と捗りました。
tasks/pkill java
Javaのプロセスを kill します。
tasks/nohup sample-1.0-SNAPSHOT/bin/sample...
新しいバージョンの Java プロセスを開始します。
Play Framework の Application Secret はコマンド実行時に受け取れるようプレースホルダー化しています。
ansible.cfg
host_key_checking
のデフォルトが True
なので、CIが途中で止まってしまうのを防ぐために無効化しておきます。
[defaults] host_key_checking = False
Playbook の実行
作成した Invetory File と Playbook を指定して実行します。
--extra-vars
オプションで Play Framework の Application Secret を指定しています。
$ ansible-playbook -i dev deploy.yml --extra-vars "secretKey='xxx'"
.gitlab-ci.yml の実装
stages: - package - deploy dist: stage: package image: hseeberger/scala-sbt:8u141-jdk_2.12.3_1.0.1 script: - >- sbt -sbt-dir .sbt -ivy .ivy2 root/dist artifacts: paths: - target expire_in: 2 weeks cache: paths: - .sbt - .ivy2 policy: pull .deploy_job: &deploy_job stage: deploy image: horikoshidockertutorial/ansible-ubuntu:latest before_script: - eval $(ssh-agent -s) - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null - mkdir -p ~/.ssh - chmod 700 ~/.ssh - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config' script: - ansible-playbook -i ansible/$ENV ansible/deploy.yml --extra-vars "secretKey='$APP_SECRET_KEY'" when: manual deploy dev: <<: *deploy_job variables: ENV: dev deploy prod: <<: *deploy_job variables: ENV: prod only: - master
以下、GitLab Runner から参照する環境変数。
下記より、詳細に見ていきます。
stages
package と deploy の2つのステージを用意しました。必要に応じてユニットテストやフォーマッタのステージを追加してあげてください。
dist
package ステージのタスクです。
- script にパッケージングのコマンドを指定します。
- artifacts に deploy のステージで使用するパッケージングの結果を指定します。
- cache を定義して依存ライブラリのキャッシュしておきます。
.deploy_job
deploy ステージの共通化されたジョブです。
image
CI用に ansible と ssh-client が使える Docker Image を作りました。下記、 Dockerfile
です。
FROM ubuntu:xenial RUN apt-get update && \ apt-get install -y software-properties-common && \ apt-add-repository --yes --update ppa:ansible/ansible && \ apt-get install -y ansible=2.7.8-1ppa~xenial && \ apt-get install -y openssh-client
horikoshidockertutorial
という怪しげなリポジトリ(使うかどうかは自己責任でお願いします)を参照していますが、実際には gitlab の Container Registry を利用しました。
before_script
GitLab Runner が Remote Host へ SSH 接続するための設定をしています。
秘密鍵を環境変数 $SSH_PRIVATE_KEY
から読めるようにしています。*5
script
ansible-playbook
コマンドを実行します。
変数 $ENV
によって Invetory File の指定および、デプロイする環境の向きを切り替えられるようにしました。
また、Play Framework の Application Secret を環境変数 $APP_SECRET_KEY
に設定し、--extra-vars
オプションから Playbook にて利用できるようにしました。
when
任意のタイミングで実行したかったので manual
を指定しました。
deploy dev, prod
.deploy_job
を参照し、各環境へのデプロイジョブを定義します。
- 変数
$ENV
を環境に応じて指定します。 - prod だけ master のみ実行できるようにしました。
実際に動かしてみると下記のようログが出力されデプロイに成功していることが確認できます。
終わりに
初めての Ansible でしたが思いのほか学習コストがかかってしまい、同じチームメンバのサポートを受けつつやり遂げることができました。
モジュールがとても充実しているので使いこなせたらとても強力だと思います。社内では Ansible への関心が高まってきているのでしっかりと使いこなせるようになりたいです。
では、また。