FLINTERS Engineer's Blog

FLINTERSのエンジニアによる技術ブログ

Ansible + GitLab Runner で作るデプロイジョブ

中途三年目、堀越です。

Ansible で GitLab CI/CD 上にデプロイジョブを実装しましたのでそのアウトプットです。

www.ansible.com

はじめに

もともとSSHしつつポチポチとシェルを叩いて Play Framework をデプロイしていたのですが、自動化しましょうという話を開発チームでしました。

Ansible とか使ったら便利そうという意見があり、以前から関心があったということで便乗して担当させていただく運びとなった次第です。

旧デプロイ環境

踏み台サーバーにシェルスクリプトが配置してあり、叩いてみると下記の処理が一連して実行される仕組みになっていました。WEBサーバーは production と development の2つの環境が存在していました。*1

踏み台サーバーにて
  1. コード最新化
    • git fetch, checkout, pull などする*2
  2. パッケージ
    • sbt clean dist
  3. SCPしてファイル転送
    • ZIPファイルを Host に転送
Host にて
  1. ZIPファイル解凍
  2. Javaプロセス kill → run

図で書くと下記のような感じです。

f:id:t_horikoshi:20190317090547j:plain
旧デプロイ

新デプロイ環境

大きくやりたいことは変わりませんが多段SSHという仕組みを使って、デプロイ関連のオペレーションを GitLab Runner から実行できるようにします。

qiita.com

図で書くと下記のような感じです。

f:id:t_horikoshi:20190317092905p:plain
新デプロイ環境

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 モジュールで一括で行えるので色々と捗りました。

docs.ansible.com

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 から参照する環境変数

f:id:t_horikoshi:20190319180152p:plain
環境変数

下記より、詳細に見ていきます。

stages

packagedeploy の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 接続するための設定をしています。

docs.gitlab.com

秘密鍵環境変数 $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 のみ実行できるようにしました。

実際に動かしてみると下記のようログが出力されデプロイに成功していることが確認できます。

f:id:t_horikoshi:20190319202707p:plain
実行ログ

終わりに

初めての Ansible でしたが思いのほか学習コストがかかってしまい、同じチームメンバのサポートを受けつつやり遂げることができました。

モジュールがとても充実しているので使いこなせたらとても強力だと思います。社内では Ansible への関心が高まってきているのでしっかりと使いこなせるようになりたいです。

では、また。

*1:いまも存在しています。

*2:git repository が配置してある前提

*3:IPアドレスは実際に存在するものではありません。

*4:53.43.34.68は踏み台サーバーのアドレスを想定しますが、こちらのIPアドレスも実際に存在するものではありません。

*5:今回、踏み台サーバーと Host の秘密鍵は共通だったのですが、異なる場合は個別に用意する必要がありそうです。