オールアバウトのリリースをよりシンプルに!本番GKEへCircleCIを導入し移行したお話
こんにちは!オールアバウトでSRE(サイト信頼性エンジニア)をしている@numa_numaです。
さて今回は、当社のクラウドネイティブな開発の加速・さらなるDevOps推進を目指し、デプロイツールにCircleCIを採用し移行しましたので、そのお話をご紹介したいと思います。
以前に、2016年くらいまでのオールアバウトでのリリースの歴史をご紹介しております。 興味のある方はぜひご参照ください。
当社では2016〜2017年頃にGKE(Google Kubernetes Engine)を利用しはじめ、Dockerコンテナで本番運用を行うノウハウを確立しました。 また、その時採用したのがWerckerというCIツールでした。
クラウド移行とその後のリリースの変遷
オールアバウトでは、GKE運用を確立するとともに、アプリケーションごとにデータセンター(オンプレミス)のサーバで稼働していたものを、徐々にGKE環境へ移行する形でクラウド化を行ってきました。
そして、2019年6月にクラウド化は完了し、データセンターのサーバ運用を完全に停止しました。 現在、オールアバウトのシステムの大半は、GKE上のサーバ(サーバ単位をPodと呼ぶ)で運用しています。
データセンターを撤退するため、WEBアプリごとに順次クラウド移行を進めましたが結果として、
- オンプレミスのアプリ: Jenkinsなど旧来の方法でリリース
- クラウド移行完了したアプリ: Werckerを利用したリリース
という区分ができていきました。
つまり、Wercker利用の最後期に実装していたのは、GKEのPodへリリースするために作られた仕組みであり、CircleCIでは構造的に同じデプロイを行いたい、というのが今回の移行のポイントでした。
Werckerを使い続ける上で生じた問題
Werckerを何か他のCIツールに代えた方が良い、というのは2017年くらいから社内であった議論でした。 理由としては、以下のようなことが挙げられていました。
- 安定性が低い・デプロイが低速(2017年当初)
- Dockerfileのビルドができない(ツールの仕様)
- Kubernetesに合った他のCIツールが充実してきた
当社では、ソースコードのリポジトリとしてBitbucketを採用しており、BitbucketとKubernetesに対応した唯一のCIツールだった(当時)というのがWercker採用の大きな決め手となりました。
Werckerの安定性が低かったのは当初の話で、運営サーバの品質が良くなかった(表示遅延/デプロイが途中で止まる)ようで、一時期しばしば問題が出ていました。 しばらく経つとツールの品質が徐々に改善され、安定性が低い・デプロイが低速という点はあまり気にならなくなっていきます。 そんな中で、当社での移行を決定的にさせたのは、OracleによりWerckerが買収されたことでした。
WerckerはOracleクラウドの一部となり、機能も強化されましたが契約体系の変更からかなりの費用増となる見込みでした。 また、当社ではアプリがデプロイできる以外でOracleクラウドを利用する予定は当面なかったため、利用ツールとして総じてWerckerが適切ではなくなってしまったことが、移行の最後の一押しとなりました。
CircleCIの選定
ここでは、当社が様々なデプロイツールの中からCircleCIをメインツールとして選定した理由について、ごく簡単に触れたいと思います。
ざっと書くと、
- GithubだけでなくBitbucketに対応していること(当社ではBitbucketを利用)
- 広く使われており、開発言語を選ばないこと
- GoogleインフラとGKEへの対応
- アカウントのセキュリティ管理の適切さ
- スケールが簡単で課金体系が分かりやすいこと
などの要件を満たしたバランスの良いツールだったためです。
社内的にはもっと細かい要求があったのですが、もともと全てを満たすものはなかったので結果的にバランス感で選んだ形です。 ちなみに、全部で20程度のツールを比較対象としていました。
CircleCIでのリリースフロー
ここまで、オールアバウトでの最近までのリリースの変遷とCircleCIの導入理由をご紹介してきました。
ここからは、CircleCIで実際どのようにリリースしているのか、一部Werckerとの比較を交えながら技術的な要点を見ていきたいと思います。
WerckerではDockerfileのビルドができないことから、Jenkinsを併用したりいろいろと運用方法が複雑になっていました。 その点、CircleCIでは1ツールで解決できるためリリースフローを単純化することができました。
CircleCIとWerckerの違い
CircleCIもWerckerもCIの実現を目的としたSaaSツールであるため、実現できる機能はおよそ同じです。 ただし、ツールとしての仕様が異なるため、実装の仕方を一部工夫する必要がありました。
CircleCIの特長
- 総じて処理が高速
- ビルド時にキャッシュすることが可能で、さらに高速化できる
- アカウント管理はGitHub/Bitbucket連携となっており、独自管理ではない
Werckerの特長
- デプロイしたコンテナにログインし、操作するワークフローが構成できる
- 過去のワークフローを再実行することで、コンテナを以前の状態に戻せる
- アカウント管理はサイト独自のものを使用
Dockerfileの移行
特にWerckerを使用していた時はコンテナにログインする機能を最大限活用していたため、移行時はDockerfileの書換えが必要となりました。 だいたい以下のような変更イメージです。
- Wercker利用時のDockerfile
FROM asia.gcr.io/gcp-project/allabout_common_image:latest # Apacheの設定 ADD dockerfile/${ENVIRONMENT}/apache/ /etc/httpd/conf.d/ # アプリ名のENVは予め、別途実行されたJenkinsビルドで付与されるためここでは不要 # アプリを使えるようにする最後のビルドは、コンテナにログインしてコマンド実行する # Expose ports EXPOSE 80
- CircleCI利用時のDockerfile
FROM asia.gcr.io/gcp-project/allabout_common_image:latest # Apacheの設定 ADD dockerfile/${ENVIRONMENT}/apache/ /etc/httpd/conf.d/ # ENVでアプリ名を定義する ENV APPNAME "allabout-pc-front" # アプリを使えるようにする最後のビルドを記述する(コンテナログインできないので、ここで処理) COPY . /app/${APPNAME} RUN cd /app/${APPNAME} \ && php /usr/local/src/composer.phar install --no-interaction # Expose ports EXPOSE 80
※ 概念を分かりやすくするため、ともに少々単純化/省略しています。
※ 当社では開発の主としてPHP(Laravel)を採用しており、アプリケーションのビルドは "composer.phar install" を実行することで行われます。
CircleCIを利用したGKEデプロイの流れ
当社でのWEBアプリケーションデプロイの大まかな流れは、以下のようになります。
- Bitbucketからソースコードをチェックアウト
- CircleCIのコンテナ上で、最初のビルド(composer.phar install)実行
- ビルドされたソースにテストを実施
- この後の処理のために、Google認証を通しておく
- docker buildを行う
- docker buildされたコンテナイメージをGCR(Google Container Registry)に格納(push)する
- Kubernetesのマニュフェストファイル群をもとに、アプリをGKEにデプロイする
以降では、これらのうちから鍵になるポイントをピックアップしながらご紹介します。
CircleCIのキャッシュ機能を活用する
ここでは、CircleCIを利用する利点でもあるビルド時のキャッシングについてご紹介します。 CircleCIではマニュアルも次々と日本語化されており、キャッシュの説明はすでに日本語化完了しているようです。
CircleCI公式:依存関係のキャッシュ https://circleci.com/docs/ja/2.0/caching/
当社でCircleCIの設定ファイル(.circleci/config.yml)から、当該箇所の定義を抜粋(コメントを追加)すると下記の通りです。
commands: # 〜中略〜 # composer_install: steps: # 前回のキャッシュを復元 - restore_cache: name: restore composer cache keys: - composer-v1-{{ checksum "composer.lock" }} - composer-v1- - run: # ビルド処理 name: composer install command: php /usr/local/src/composer.phar install --no-interaction # ビルド結果をもとに、キャッシュを保存 - save_cache: name: save composer cache key: composer-v1-{{ checksum "composer.lock" }} paths: - ./vendor
まず、キャッシュするパスですが、
paths: - ./vendor
このように定義されています。アプリケーションのディレクトリが/app/であるとすると、composer.phar installは以下のディレクトリで実行され、記載の条件でインストール結果が保持されます。
アプリ名の例: allabout-pc-front コンテナでのアプリ配置の絶対パス: /app/allabout-pc-front ビルド(composerインストール)内容の保持ディレクトリ: /app/allabout-pc-front/vendor
キャッシュされたものを使用するかの判定はcomposer.lockファイルのハッシュ値を比較して行なっています。 仮にファイルに差異があっても、vendorディレクトリは前回の内容が復元されるのでビルド時間が大幅に短縮できます。
当社の標準的なアプリですと、初回のビルドと2回目以降のビルドでのかかる時間差はおよそ下記のような違いがありました。
初回のビルド : 2分30秒 2回目以降のビルド : 5〜10秒 ※ 追加のcomposerモジュールインストールがない場合
当社の場合は主にコンテナですが、VMでも似たような結果となりました。 CircleCIを利用する場合は、ぜひビルド時のキャッシュを有効活用した方が良いでしょう。
CircleCIでのGKEへのデプロイ
次に、CircleCIを利用してのdocker buildを実施〜GKEへのデプロイまでの処理を行う箇所をご紹介します。
ここでは以下の要件でデプロイを行う例を記載します。
アプリ名: allabout-pc-front GCPプロジェクト名: gcp-project GCP利用ゾーン: asia-east-1a (台湾Aゾーン) GKEクラスタ名: allabout-stg01
CircleCIのビルトイン(定義済み)変数を利用することで、リポジトリ名(アプリ名)を得ることができます。
${CIRCLE_PROJECT_REPONAME} => allabout-pc-front
なお、Kubernetesのマニュフェストファイルなどは、リポジトリの以下パスに格納されているものとします。 マニュフェストファイルの内容については、本項の趣旨と異なるため割愛します。
dockerfile/Dockerfile … Dockerfile yaml_files/deployment-stg-allabout-pc-front.yaml … deployment.yaml yaml_files/svc-stg-allabout-pc-front.yaml … svc.yaml
※ サービスを稼働させるための最低限のマニュフェストのみを紹介しています。 スモールサービスでない限り、当社の標準的なアプリケーションなら実際には他のKubernetesリソースもデプロイします。
CircleCIの設定ファイル(.circleci/config.yml)の記載は、以下のようになります。
jobs: # 〜中略〜 # deploy_staging: # ステージングのGKEクラスタにデプロイする docker: # Google認証するので、google/cloud-sdkのイメージを利用 - image: google/cloud-sdk working_directory: ~/workspace steps: - attach_workspace: at: ~/workspace # dockerコマンドを使う時は必要 - setup_remote_docker - run: # Google認証を通す処理 → 割愛します # Googleのサービスアカウントを使って "Kubernetesクラスタ管理者" 相当の権限を持たせます name: Google Authentication (STG) command: echo "dummy" - run: # docker buildとGCRへのpush name: Docker build and push image to GCR (STG) command: | docker build \ --file=dockerfile/Dockerfile \ --build-arg ENVIRONMENT=staging \ -t ${CIRCLE_PROJECT_REPONAME} . docker tag ${CIRCLE_PROJECT_REPONAME} asia.gcr.io/gcp-project/${CIRCLE_PROJECT_REPONAME}-stg:${CIRCLE_SHA1} docker push asia.gcr.io/gcp-project/${CIRCLE_PROJECT_REPONAME}-stg:${CIRCLE_SHA1} - run: # GCPのプロジェクト/ゾーン/GKEクラスタを選択 name: Select GKE Cluster (STG) command: | gcloud config set core/project gcp-project gcloud config set compute/zone asia-east-1a gcloud container clusters get-credentials allabout-stg01 - run: # deploymentとsvcをGKEクラスタにデプロイ name: Deploy to Kubernetes (STG) command: | kubectl apply -f yaml_files/deployment-stg-${CIRCLE_PROJECT_REPONAME}.yaml kubectl apply -f yaml_files/svc-stg-${CIRCLE_PROJECT_REPONAME}.yaml
※ 概念を分かりやすくするため、ともに単純化/省略しています。またコメントを多数追加しています。
ちなみに、
${CIRCLE_SHA1}
というCircleCIビルトイン変数は、ビルド時の最終コミットのハッシュ値が自動で設定されます。 これをイメージに付加することで、GCRのpushイメージを一意にすることができます。
ややこしく見えますが、認証や準備がほとんどで実際の処理の本質は以下のみです。
kubectl apply -f yaml_files/deployment-stg-allabout-pc-front.yaml kubectl apply -f yaml_files/svc-stg-allabout-pc-front.yaml
システム管理者が手運用でKubernetesクラスタにPodデプロイを行うことと、何ら変わりません。 コンテナの世界ではこの操作でドラスティックな変更が加わるため、常に安全に実行するためCIで自動化します。
まとめ
- CircleCIは高速であり、キャッシュを活用することでさらに高速化を実感できる
- SaaS→SaaSへのCIツール移行は、アーキテクチャの理解があればハードルは高くない
- CircleCIからGKEへのデプロイは、docker/Kubernetesの基本に従って実装可能
品質が安定した後のWerckerも良いツールでしたが、CircleCIは高速な処理が行える高品質なツールという印象が強かったです。 CircleCIの機能的特徴によって、結果的にオールアバウトのデプロイ基盤はコンテナの基本に忠実なものへ整理することができました。