はじめに
アプリケーションは Rails と Vue.js を想定して、実戦的なアプリケーション動作環境を構築するための方法を段階的に紹介していきます。
Rails と Vue を使ったアプリケーションを初めて開発する場合は Ruby on Rails, Vue.js で始めるモダン WEB アプリケーション入門 も参考にしてみて下さい。
アプリケーションの機能
- (その1) アプリケーションは冗長構成の Postgres に接続できる
- (その1) アプリケーション、DB は k8s にデプロイされる
- (その1) LE証明書を使う(定期更新できる)
- (その2) CI/CD 環境がある
- (その3) OAuth によりログイン認証できる
CD環境を構築する
前回は本番環境を初期構築しました。
今後、アプリケーションが新しくなった時に(ほぼ)自動でデプロイできるようにしてみます。
Git リポジトリの stable ブランチが本番環境用なので、stable ブランチが更新されたことを起因としてデプロイ処理が行われるようにしましょう。
インテグレーションツールには Jenkins, CircleCI や Werkrer, GitHub Action 等があり、どのツールでもよいと思います。
今回は GitHub でアプリケーションを管理することを想定しているため、GitHub Action を使うことにします。
デプロイに必要な処理の概要は次のとおりです。
- コンテナイメージをビルドして Docker hub に push する
- イメージのタグ名は
stabe
(1-a.),<Git SHA-1ハッシュ>
(1-b.) の2種類がある
- イメージのタグ名は
- DB のマイグレーションを実行する
- Rails アプリケーションを新しいコンテナイメージで動作させる
Docker hub の automated build を設定する(1-a.)
Docker hub ではリポジトリを GitHub と連携して Automated build を行う設定が出来ます。
しかしタグ名に Git SHA1 ハッシュ値を設定する等の細かい設定は GitHub Action にて行うこととして、stable ブランチを stable タグで build する設定だけすることにします。
automated build を設定することで副産物として Docker hub の repository に GitHub の README.md が同期されます。
automated build を設定するには、リポジトリの設定から [Builds] タブから設定します。参考
|Source Type|Source|Docker Tag|Dockerfile location|Build Context|Autobuild|Build Caching|
| --- | --- | --- | --- | --- | --- | --- | --- |
|Branch|stable|stable|Dockerfile|/|ON
|ON
|
設定が終わったら一度 Trigger ボタンを押してビルドを実行してみるとよいでしょう。
ビルドが成功して General タブに README.md の内容が表示され、Tags に stable タグのイメージが作成されていれば成功です。
GitHub Action を設定する(1-b., 2., 3.)
次に GitHub Action を設定します。
stable ブランチのコミット値をタグ名に持つ Docker コンテナイメージをビルドしてプッシュする(1-b.)
まずは stable ブランチにコミットが push されたときにコミットハッシュ値をタグ名に持つ Docker コンテナイメージをビルドしてプッシュするアクションを追加します。
docker で実行するコマンドを考え、そのコマンドを GitHub Action にすることにします。
$ docker build . --file Dockerfile --tag <Dockerユーザ名>/vue_practice_app:<Gitコミットハッシュ>
$ docker login -u <Docker hubユーザ名> -p <Docker hubパスワード>
$ docker push <Dockerユーザ名>/vue_practice_app:<Gitコミットハッシュ>
カッコ内の値は GitHub Action から参照できるよう、Secrets 又は GitHub Action のコンテキストを使います。
項目 | 種別 | 参照方法 |
---|---|---|
<Docker hubユーザ名> | Secrets | ${{ secrets.DOCKER_HUB_USERNAME }} |
<Docker hubパスワード> | Secrets | ${{ secrets.DOCKER_HUB_PASSWORD }} |
<Gitコミットハッシュ> | GitHub Action変数 | ${{ github.sha }} |
※ ${{}}
で括られた内容は GitHub Action のコンテキストです
Secrets を登録した後、Docker コマンドを GitHub Action にします。
name: Docker Image CI
on:
push:
branches:
- stable
jobs:
docker_build_and_push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Build the Docker image
env:
# [TODO] ${{ github.repository }} から repository 名だけ抽出する
IMAGE_NAME: ${{ secrets.DOCKER_HUB_USERNAME }}/vue_practice_app:${{ github.sha }}
run: docker build . --file Dockerfile --tag $IMAGE_NAME
- name: Login to Docker hub
run: docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} -p ${{ secrets.DOCKER_HUB_PASSWORD }}
- name: Push the Docker image to Docker hub
env:
# [TODO] ${{ github.repository }} から repository 名だけ抽出する
IMAGE_NAME: ${{ secrets.DOCKER_HUB_USERNAME }}/vue_practice_app:${{ github.sha }}
run: docker push $IMAGE_NAME
DB のマイグレーションを実行する(2.)
次に DB のマイグレーションを実行するアクションを追加します。
マイグレーションファイルが追加された場合は、DB マイグレーションを実行する必要があります。
マイグレーションファイルが変更されてない時にコマンドを実行しても影響はないため、リリーズ前には必ず DB マイグレーションを実行することにします。
kubectl で実行するコマンドを考え、そのコマンドを GitHub Action にすることにします。
$ kubectl run tmp-migrate -i --generator=run-pod/v1 --rm \
--image <Docker hubユーザ名>/vue_practice_app:<Gitコミットハッシュ> \
--overrides='{"spec":{"containers":[{"name":"app","image":"<Docker hubユーザ名>/vue_practice_app:<Gitコミットハッシュ>","command":["bash"],"args":["-c","rails db:migrate SECRET_KEY_BASE=$(rails secret)"],"envFrom":[{"secretRef":{"name":"vue-practice"}}]}]}}'
アクションを実行する条件ですが、先に作成したコンテナビルドアクションが成功した場合に実行すればよいのですが、Workflow を複数数珠つなぎにさせることは出来ません。参考
実行しているワークフローのアクションからは、新しいワークフローの実行をトリガーできません。 たとえば、リポジトリの GITHUB_TOKEN を使ってアクションがコードをプッシュしている場合、push イベントの発生時には、そのリポジトリが、新しいワークフローを実行するよう設定されているワークフローを含んでいる場合であっても、新しいワークフローは実行できません。
そこで Release を作成したタイミングでデプロイを行うように設定します。
尚、kubectl コマンドを実行する GitHub Action として kubernetes-cli-kubectl を使ってみました。
※ --overridesに指定するJSONパスを渡すときの文字列がそのまま使えずにダブルクォート "
をエスケープする必要がありハマり使いづらさを感じました。本家の改善案や kubectl コマンドを実行できる Docker イメージを使うことで改善できる可能性があります
name: Deploy docker container to kubernetes
on:
release:
types: [published]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: migrate on cluster
uses: steebchen/kubectl@master
env:
KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_DATA }}
with:
args: >
run tmp-migrate -i --generator=run-pod/v1 --rm
--image ${{ secrets.DOCKER_HUB_USERNAME }}/vue_practice_app:${{ github.sha }}
--overrides='{
\"spec\":{
\"containers\":[{
\"name\":\"app\",
\"image\":\"${{ secrets.DOCKER_HUB_USERNAME }}/vue_practice_app:${{ github.sha }}\",
\"command\":[\"bash\"],
\"args\":[\"-c\",\"rails db:migrate SECRET_KEY_BASE=$(rails secret)\"],
\"envFrom\":[{\"secretRef\":{\"name\":\"vue-practice\"}}]
}]
}
}'
Rails アプリケーションを新しいコンテナイメージで動作させる(3.)
次にアプリケーションを新しいコンテナイメージで動作させるアクションを追加します。
kubectl で実行するコマンドを考え、そのコマンドを GitHub Action にすることにします。
$ kubectl set image --namespace vue-practice --record \
deployment/vue-practice app=<Docker hubユーザ名>/vue_practice_app:<Gitコミットハッシュ>
$ kubectl rollout status --namespace vue-practice deployment/vue-practice
これらをアクションにします。
name: Deploy docker container to kubernetes
on:
release:
types: [published]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: migrate on cluster
uses: steebchen/kubectl@master
env:
KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_DATA }}
with:
args: >
run tmp-migrate -i --generator=run-pod/v1 --rm
--image ${{ secrets.DOCKER_HUB_USERNAME }}/vue_practice_app:${{ github.sha }}
--overrides='{
\"spec\":{
\"containers\":[{
\"name\":\"app\",
\"image\":\"${{ secrets.DOCKER_HUB_USERNAME }}/vue_practice_app:${{ github.sha }}\",
\"command\":[\"bash\"],
\"args\":[\"-c\",\"rails db:migrate SECRET_KEY_BASE=$(rails secret)\"],
\"envFrom\":[{\"secretRef\":{\"name\":\"vue-practice\"}}]
}]
}
}'
- name: deploy to cluster
uses: steebchen/kubectl@master
env:
KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_DATA }}
# [TODO] ${{ github.repository }} から repository 名だけ抽出する
IMAGE_NAME: ${{ secrets.DOCKER_HUB_USERNAME }}/vue_practice_app:${{ github.sha }}
K8S_NAMESPACE: vue-practice
K8S_DEPLOYMENT_NAME: vue-practice
K8S_CONTAINER_NAME: app
with:
args: set image --namespace $K8S_NAMESPACE --record deployment/$K8S_DEPLOYMENT_NAME $K8S_CONTAINER_NAME=$IMAGE_NAME
- name: verify deployment
uses: steebchen/kubectl@master
env:
KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_DATA }}
K8S_NAMESPACE: vue-practice
K8S_DEPLOYMENT_NAME: vue-practice
with:
args: rollout status --namespace $K8S_NAMESPACE deployment/$K8S_DEPLOYMENT_NAME
CD環境の動作テストをする
次の機能を持つ CD 環境が構築できました。
- stable ブランチが更新(push) されると Docker コンテナイメージがビルドされる
- タグ
stable
のコンテナイメージが Docker hub の autobuild 設定により自動でビルドされる - タグ
<Gitコミットハッシュ値>
のコンテナイメージが GitHub Action の Workflow によりビルドされる
- タグ
- GitHub の Release が作成(published) されると GitHub Action の Workflow によりアプリケーションがデプロイされる
それぞれの機能について動作を確認してみましょう。
- master ブランチを stable ブランチにマージする PR を作成してマージする
- Docker hub を確認して
stable
タグと<Gitコミットハッシュ値>
のイメージが追加されれば成功です
- Docker hub を確認して
- stable ブランチをリリースする
- GitHub の Release タブから stable ブランチを指定してリリースします
- タグは新規作成又は既存から選択します
CI環境を構築する
CI環境を構築していきます。
CI環境で行う処理の概要は次のとおりです。
- テスト DB のマイグレーションを実行する
- minitest を実行する
コマンドは次のとおりです。
$ bundle install
$ bundle exec rails db:migrate
$ bundle exec rails test
アクションは次のようになります。
どのブランチでも実行するように on: push
を指定します。
name: Test
on: push
jobs:
test:
runs-on: ubuntu-latest
container: ruby:2.5.3
steps:
- uses: actions/checkout@v1
- name: Initialize
env:
RAILS_ENV: test
DISABLE_SPRING: 1
run: |
# install tools
curl -sL https://deb.nodesource.com/setup_10.x | bash -
apt-get install -y nodejs
npm install yarn@1.13.0
gem install bundler -v 1.17.3
# initialize DB
bundle install
bundle exec rails db:migrate
- name: Test
run: bundle exec rails test
アクションが出来たら master ブランチに PR をマージできる条件として、アクションがパスすることを追加しておきましょう。
さいごに
これで Rails アプリケーションを開発するための CI/CD 構築は終わりです。
次はアプリケーションに OAuth 認証が出来るよう機能を追加することにします。