GitLab と Dokku (と一部 Heroku) を使って CI/CD (Continuous Integration / Continuous Deployment) 環境を作ってみた話の続きです。 CI 部分のメインとなる .gitlab-ci.yml の設定の別パターンの話です。 review 環境は Dokku を使って、 staging と production に Heroku を使います。

gitlab カテゴリーで一覧が見えます。

対象バージョン

  • Ubuntu 16.04.2 LTS
  • Omnibus GitLab 9.1.4-ce.0 (インストール時) から 9.3.5-ce.0 (記事執筆時)
  • gitlab-ci-multi-runner 9.2.0 (インストール時) から 9.3.0 (記事執筆時)
  • Dokku 0.9.4 (インストール時) から 0.10.2 (記事執筆時)

構成

今回は、 git push すると CI が走り、 master 以外のブランチなら review 環境にデプロイされて、 master ブランチなら staging 環境にデプロイして、 tag が push されたら production 環境にデプロイされる、 という状態にします。

そして、 staging と production の deploy 先は Dokku ではなく Heroku を使います。

.gitlab-ci.yml

この後は .gitlab-ci.yml に設定する内容の説明になります。

image 設定

ここは前回と同じです。

ここでは library/ruby - Docker Hub の 2.3.3 を使いました。 .ruby-versionGemfile と同じバージョンを指定します。

image: ruby:2.3.3

2.3 だと 2.3.4 になるので、 2.3.3 まで指定しています。 2.3.4 はセキュリティアップデートではなかったので、まだ 2.3.3 のままですが、様子をみてあげる予定です。

cache 設定

ここも前回と同じです。

cache:key から per-branch caching を選んで以下のように設定しました。

cache:
  key: "$CI_COMMIT_REF_NAME"
  untracked: true

環境変数設定

ここも前回と同じです。

library/postgres - Docker Hub で使う POSTGRES_PASSWORD などと、それに接続するための Rails 用の DATABASE_URL、 デプロイ用の省略表記のための DOKKU などを設定しています。

APP_NAMEDB_NAMECI_ENVIRONMENT_SLUG を使ってブランチごとに自動生成される名前を使っています。

variables:
  # for test
  POSTGRES_DB: dbname
  POSTGRES_USER: dbuser
  POSTGRES_PASSWORD: dbpass
  DATABASE_URL: "postgres://dbuser:dbpass@postgres:5432/dbname"
  # for deploy
  DOKKU: ssh dokku@$DOKKU_HOST
  APP_NAME: $CI_ENVIRONMENT_SLUG
  DB_NAME: $CI_ENVIRONMENT_SLUG-database

stages 設定

ここも設定内容は前回と同じですが、 staging と production が別 Pipeline になって、それぞれで test が実行されるのが違います。

最初に説明したように、 test の後に review、 staging、 production となるように stages を設定します。

stages:
  - test
  - review
  - staging
  - production

before_script

ここも前回と同じです。

テスト用の before_script を設定しています。(デプロイの方は個別に上書きしています。)

apt や bundler ではキャッシュ用のディレクトリである /cache を使うように指定しています。

開発環境と共通になっている都合上、 sqlite3 を入れています。 JavaScript ランタイムも必要なので、 nodejs も入れています。

before_script:
  - 'apt-get update -qq && apt-get -o dir::cache::archives="/cache/apt" install -y -qq sqlite3 libsqlite3-dev nodejs'
  - ruby -v
  - gem install bundler --no-ri --no-rdoc
  - bundle install --jobs $(nproc) --path=/cache/bundler
  - ln -nfs .test.env .env

デプロイ用の before_script

ここも前回と同じです。

Hidden keysに書いてあるようにキーが . で始まるものは無視されるので、 YAML のアンカーを使って参照用に使えます。

ruby image には openssh-client は入っていたのですが、他の image に変えても動くように参考にした Using SSH keys に書いてあった通り、 openssh-client のインストール手順も入れています。

~/.ssh/config で Hostname や Port や User などを指定したかったことがあったので、設定できるようにしました。

.before_ssh: &before_ssh
  # https://docs.gitlab.com/ce/ci/ssh_keys/README.html
  - 'which ssh-agent || ( apt-get update -y && apt-get -o dir::cache::archives="/cache/apt" install -y openssh-client )'
  - eval $(ssh-agent -s)
  - ssh-add <(echo "$SSH_PRIVATE_KEY")
  - mkdir -p ~/.ssh
  # Set `ssh-keyscan $DOKKU_HOST` to SSH_SERVER_HOSTKEYS
  - '[[ -f /.dockerenv ]] && echo "$SSH_SERVER_HOSTKEYS" > ~/.ssh/known_hosts'
  - '[[ -f /.dockerenv ]] && echo "$SSH_CONFIG" > ~/.ssh/config'

GitLab の Web での環境変数設定

ここも前回とほぼ同じですが、 production と staging の deploy 用の API 鍵を設定しているところが違います。

ここで .gitlab-ci.yml の話は中断して、 Secret variables の設定の話です。

GitLab で該当プロジェクトを開いて、 Settings の Pipelines から Secret variables で SSH_PRIVATE_KEY などを設定します。

ここでは、以下のように設定しました。

DOKKU_HOST Dokku に ssh するときのホスト名 (dokku.example.com など)
DOKKU_DOMAIN Dokku の VHOST に設定したドメイン (10.1.2.3.xip.io など)
SSH_PRIVATE_KEY 秘密鍵 (~/.ssh/id_gitlab など) の内容
SSH_SERVER_HOSTKEYS ssh-keyscan $DOKKU_HOST の出力
SSH_CONFIG ~/.ssh/config に設定したい内容
HEROKU_PRODUCTION_API_KEY Manage Account から production 用の Heroku アカウントの API key
HEROKU_STAGING_API_KEY Manage Account から staging 用の Heroku アカウントの API key

SSH_PRIVATE_KEY は Protected を Yes にすると review 環境への deploy に失敗するので、 No のままにする必要がありそうです。

HEROKU_PRODUCTION_API_KEYHEROKU_STAGING_API_KEY は Protected branch などの運用次第で Protected を Yes にできそうです。

review 環境への deploy 用 script

ここも前回とほぼ同じです。

まず Dokku でアプリとデータベースを作成して接続します。 2度目以降は同じアプリを更新するので、作成などのエラーは無視します。

環境変数は TZRAILS_SERVE_STATIC_FILES あたりがほぼ必須だと思いますが、他は staging 環境や review 環境用に独自に設定できるようにしています。 RACK_DEV_MARK_ENV は rack-dev-mark gem の設定です。

Procfile で web だけではなく clockwork gem を使ったプロセスも動かしている関係で letter_opener 用のディレクトリをマウントしていますが、 /usr/bin/find '/var/lib/dokku/data/storage/letter_opener' -mtime '+2' -delete のような感じで古いファイルは自動削除する予定です。 (自動削除はまだしていません。)

デプロイ本体部分の git push は、単純に master だとうまくいかないことがあったので、 HEAD:refs/heads/master という指定にしています。 Heroku にデプロイするときも git でデプロイするなら同様になります。

db:seed の実行は ssh-tt をつけて強制的に tty を確保する必要がありました。

.deploy_script: &deploy_script
  - $DOKKU apps:create $APP_NAME || echo $?
  # require `sudo dokku plugin:install https://github.com/dokku/dokku-postgres`
  - $DOKKU postgres:create $DB_NAME || echo $?
  - $DOKKU postgres:link $DB_NAME $APP_NAME || echo $?
  - $DOKKU config:set --no-restart $APP_NAME
    TZ=Asia/Tokyo
    RAILS_SERVE_STATIC_FILES=1
    NO_FORCE_SSL=1
    RACK_DEV_MARK_ENV=review
  - git push dokku@$DOKKU_HOST:$APP_NAME HEAD:refs/heads/master
  - $DOKKU -tt run $APP_NAME bundle exec rake db:seed

前回と違って RAILS_ENV の変更をしていなかったり、 letter_opener_webseed_fu は使っていないという違いがあります。

test stage のジョブ

ここも前回と同じです。

postgres を使って rake でテストを走らせます。

データベースの設定は DATABASE_URL で指定しているので、 config/database.yml は特に何もしていません。

rake:
  stage: test
  services:
    - postgres:latest
  script:
    - bundle exec rake db:setup RAILS_ENV=test
    - bundle exec rake

rubocop なども使うなら同様に設定します。

production 環境への deploy

順番が前後しますが、最初に production 環境への deploy 設定です。

production 環境はちゃんと名前が決まっているので APP_NAME を上書きします。

Heroku への deploy には Test and Deploy a ruby application に書いてある dpl gem を使っています。 git push による deploy の方が好みなら git push を使っても良いと思います。

environment を設定することで GitLab の Web のプロジェクトの Pipelines の Environments からリンクが貼られます。

only で tags を指定することで tag が push されたら開始するようにしています。

only で master のみに制限しています。

production:
  stage: production
  variables:
    APP_NAME: hello-app
  script:
  - gem install dpl
  - dpl --provider=heroku --app=$APP_NAME --api-key=$HEROKU_PRODUCTION_API_KEY
  environment:
    name: production
    url: https://$APP_NAME.herokuapp.com/
  only:
  - tags

staging 環境への deploy

production 環境と同様に staging 環境への deploy 設定をしています。

APP_NAME や API key や URL などが違う以外は基本的に production と同じです。

staging:
  stage: staging
  variables:
    APP_NAME: hello-staging-app
  script:
  - gem install dpl
  - dpl --provider=heroku --app=$APP_NAME --api-key=$HEROKU_STAGING_API_KEY
  environment:
    name: staging
    url: https://$APP_NAME.herokuapp.com/
  only:
  - master

review 環境への deploy

review 環境への deploy は前回と同じです。

deploy 本体の script は事前に定義した deploy_script を使います。 結局ここでしか使っていないので、直接ここに書いても良かったかもしれません。

environment は name に review/ をつけることで複数の review 環境が同時に存在している時に折りたたまれるようになります。

review 環境は動的に作ったり消したりするので、 https ではなく http になっています。

on_stop を指定することで環境の削除ジョブを指定できます。

only と except で master 以外のブランチの時に review 環境が作成されるようにしています。

review:
  stage: review
  before_script: *before_ssh
  script: *deploy_script
  environment:
    name: review/$CI_COMMIT_REF_NAME
    url: http://$CI_ENVIRONMENT_SLUG.$DOKKU_DOMAIN
    on_stop: stop_review
  only:
    - branches
  except:
    - master

review 環境の削除

ここも前回と同じです。

action: stop で環境を削除するジョブとして設定しています。

when: manual で手動実行するように設定していますが、基本的にはマージリクエストがマージされた時に Remove source branch にチェックを入れて、自動で停止しています。

GIT_STRATEGY: none で git 関連の操作はせずに速やかに停止処理のみするようにしています。

postgres は使用中だと停止できないので、先にアプリケーションを削除してからデータベースを削除しています。 自動実行なので --force で確認なしに削除するようにしています。

stop_review:
  stage: review
  variables:
    GIT_STRATEGY: none
  before_script: *before_ssh
  script:
    - $DOKKU apps:destroy $CI_ENVIRONMENT_SLUG --force || echo $?
    - $DOKKU postgres:destroy $CI_ENVIRONMENT_SLUG-database --force || echo $?
  environment:
    name: review/$CI_COMMIT_REF_NAME
    action: stop
  when: manual
  only:
    - branches
  except:
    - master

まとめ

GitLab CI と Dokku と Heroku を組み合わせて CI/CD 環境を作る例を紹介しました。

今回紹介した様に、 Heroku に似ていてもある程度違いのある Dokku で自由に review 環境を作成して、 production に近い方が良い staging 環境は Heroku を使うというのが、自前の環境のリソースには余裕がある場合には良いのではないでしょうか。

GitLab + Dokku 関連の記事はgitlab カテゴリーで一覧が見えます。

Disqus Comments

Kazuhiro NISHIYAMA

Ruby のコミッターとかやってます。 フルスタックエンジニア(って何?)かもしれません。 About znzに主なアカウントをまとめました。

znz znz


Published