本書は@pospomeがサーバサイドアプリケーションのデプロイフローに関してまとめたものです。
pospome。サーバサイドエンジニア。DDD、実装パターンのようなアプリケーションアーキテクチャが専門。最近は認証認可に興味がある。Twitterは@pospome。ブログは「pospomeのプログラミング日記」。共にアプリケーションアーキテクチャ、Go言語、GCPを中心にアウトプットしている。キャラクターは羊ではなくポメラニアン。
本書では図1.1のデプロイフローを扱います。(画像は拡大して見てください)
解説対象となる技術は以下です。
本章はアプリケーションのデプロイフローを一通り体験することができるチュートリアルです。最低限必要な基礎知識を説明しながら可能な限り最短でアプリケーションをデプロイすることを目的としています。各技術の細かい設定は他の章で説明しているのでそちらを参照してください。
最初にデプロイ対象となるアプリケーションを作成しましょう。本書では筆者が使い慣れているGoのWebサーバを対象としますが、デプロイ対象はDockerのイメージであればGoのWebサーバである必要はありません。Goのインストールは省略します。
リスト2.1がWebサーバの実装です。"Hello"という文字列を表示するだけのシンプルな実装です。
リスト2.1をmain.goというファイル名で保存しましょう。ディレクトリ構造はリスト2.2のようになります。
ローカルマシンでWebサーバを起動してみましょう。(リスト2.3)
Webサーバが起動できたらcurlで動作確認しましょう。"Hello"という文字列が表示されれば成功です。(リスト2.4)
Dockerfileを作成します。Dockerのインストールは省略します。
Dockerfileは"Start a Go instance in your app"を参考にします。Dockerfileはリスト2.5です。
Dockerfileを配置したディレクトリ構造はリスト2.6です。
Dockerfileが正しいことを確認するためにローカルマシンでDockerイメージを作成します。リスト2.7のコマンドを実行してください。
作成したDockerイメージを確認します。リスト2.8のコマンドを実行してください。
Dockerイメージが正しく起動することを確認するためにローカルマシンで起動します。リスト2.9のコマンドを実行してください。
Dockerイメージが起動していることを確認します。リスト2.10のコマンドを実行してください。
curlで動作確認します。リスト2.11のコマンドを実行して"Hello"という文字列が出力されれば成功です。
GoのWebサーバ実装とDockerfileを配置したディレクトリをGitHubのリポジトリにpushしてください。ここではプライベートリポジトリを作成します。(図2.1)
本書ではGCP(Google Cloud Platform)を利用するのでCLOUD SDKをセットアップする必要があります。Cloud SDKはコマンドラインでGCPのリソースを操作できるSDKです。Cloud SDKをインストールすると"gcloud"コマンドを実行することができます。こちらから任意のOSを選択してCLOUD SDKをインストールしてください。筆者はMacを利用しているのでこちらの手順でインストールしました。"gcloud の主要なコマンドの実行"まで進めてリスト2.12,リスト2.13のコマンドにてアカウントが正しく設定されていることを確認できればセットアップ完了です。
ここに利用するGCPプロジェクトを作成します。GCPにおけるプロジェクトなどのリソースの概念はこちらに載っています。プロジェクトについてはこちらに載っています。プロジェクトの上位には"組織リソース"が存在しますが、組織リソースを持つのはG SuiteまたはCloud Identityのユーザーだけです。個人でGCPを利用する場合はプロジェクトが最上位のリソースになるでしょう。
GCPのプロジェクトを作成するためにプロジェクトIDを決めます。組織リソースが存在しない場合、プロジェクトIDは他のユーザーも含めたGCP全体でユニークでなければいけません。例えば"sample"のようなありきたりな名前を指定してプロジェクトを作成するとリスト2.14のエラーが発生します。
筆者はプロジェクトIDとして"pospome-deployflow-project"を指定してプロジェクトを作成します。皆さんもユニークなプロジェクトIDを指定してGCPプロジェクトを作成してください。プロジェクトの作成はリスト2.15のコマンドを実行してください。
本書の中でGCPのプロジェクトIDに依存しているコマンドやURLには"pospome-deployflow-project"のような個人に依存する値が含まれています。それらのコマンドやURLは随時自分のプロジェクトIDに置き換えてください。
GCPにはゾーンという概念が存在します。ゾーンを一言で説明するとリソースのホスト先です。例えばGCPにおける仮想マシンであるGCE(Google Compute Engine)を利用する場合、その仮想マシンをホストする場所としてゾーンを選択する必要があります。当然ながらホスト先が地理的に近ければ近いほどリソースへのアクセスは高速になります。例えば日本でWebサービスを展開する場合は日本のゾーンを選択した方がよいでしょう。しかし、ゾーンによっては特定のリソースをサポートしていないことがあるので注意しましょう。ゾーンについて詳しく知りたい場合はこちらを参照してください。
CLOUD SDKは複数のプロジェクトと複数のゾーンを扱うことができるので、プロジェクトとゾーンに依存する操作では対象となるプロジェクトとゾーンを指定する必要があります。ゾーンを指定せずにコマンドを実行した場合はリスト2.16のエラーが発生します。
プロジェクトとゾーンの指定方法は2つあります。1つ目はリスト2.17のように"--project","--zone"オプションで指定する方法です。
2つ目はリスト2.18のようにデフォルト値としてセットしておく方法です。
本書ではコマンドごとに異なるプロジェクトとゾーンを扱うことはありません。プロジェクトとゾーンを毎回指定するのは面倒なのでここではデフォルト値としてセットします。ここではゾーンとして"asia-east1-b"を利用します。リスト2.18のコマンドを実行してプロジェクトとゾーンをデフォルト値としてセットしてください。プロジェクトIDとして指定している"pospome-deployflow-project"は自分が作成したプロジェクトIDに置き換えてください。リスト2.18を実行し終わったらリスト2.19を実行してゾーンとプロジェクトがセットされていることを確認してください。
GCPではCLOUD SDKにて初めて操作するリソースに対してAPIを有効にする必要があります。例えばリスト2.20は利用可能なゾーンの一覧を表示するコマンドですが、今までGCEのリソースに対する操作をしたことがない場合、APIを有効にするかを確認するメッセージが表示されます。
ここではAPIの有効化を省略しているのでリスト2.20のようなメッセージが表示された場合、"y"を入力してAPIを有効にしてください。
ここではGCPの課金設定を有効にする必要があります。こちらを参考にGCPの請求先アカウントを作成してください。請求先アカウントを作成したらリスト2.21のコマンドを実行して確認します。
請求先アカウントが確認できたらリスト2.22のコマンドを実行して請求先アカウントとGCPプロジェクトを紐づけましょう。"--billing-account"のオプションにはリスト2.21で表示される"ACCOUNT_ID"を指定します。
請求先アカウントとGCPプロジェクトを紐付けることができたら利用可能なゾーンの一覧を表示してみましょう。リスト2.23のコマンドを実行してください。課金設定とGCEのAPIを有効にするとゾーンの一覧が表示されるはずです。
課金設定がされていない場合はリスト2.24のようなエラーが表示されます。
GitHubのリポジトリへのpushをCircleCIが検知できるようにします。
GitHubとCircleCIを連携します。まずはCircleCIにログインしてください。今回はGitHubを利用しているのでGitHubアカウントを利用してログインします。(図2.2)
ログインすると図2.3のような画面が表示されます。
左メニューの"ADD PROJECTS"から連携対象となるCircleCIリポジトリの"Set Up Project"ボタンをクリックします。(図2.4)
図2.5のような"Set Up Project"のページが表示されれば連携は完了です。
GitHubのリポジトリへのpushをトリガーにCircleCIでジョブを動かしてみましょう。"Set Up Project"のページ内の一番下には"Sample .yml File"というセクションがあり、リスト2.25のようなYAMLが表示されていると思います。
リスト2.25はCircleCIで実行するジョブの定義ファイル(YAML形式)です。リスト2.25はサンプルということもあり、シンプルなジョブです。具体的には"circleci/golang:1.9"というGo1.9のDockerイメージで、"go get -v -t -d ./...","go test -v ./..."を実行しているだけです。つまり、Goのテストを実行しているだけのジョブです。リスト2.25を今回のWebサーバ用に書き直したものがリスト2.26です。
修正点は以下です。
"{{ORG_NAME}}","{{REPO_NAME}}は環境変数で渡す必要がないのでハードコーディングに変更しました。ジョブを実行するDockerイメージはGo1.9からGo1.11に変更しました。ここでデプロイするgo-web-serverにはテストが存在しないので"go test"を実行する意味もないのですが、"go test"を実行してジョブが失敗することもないので削除せず残しています。
CircleCIはGitHubリポジトリのルートディレクトリに存在する".circleci/config.yml"を参照し、そのファイルに記載されている処理を実行します。リスト2.26をGitHubリポジトリのルートディレクトリに".circleci/config.yml"として設置してください。ディレクトリ構造はリスト2.27になります。
config.ymlの設置が終わったらそれらをcommitしてリポジトリのmasterブランチにpushしてください。CircleCIのジョブはpushをトリガーにして実行されるので、ジョブが実行されるはずです。CircleCIのジョブはCircleCIの左メニューの"JOBS"で確認することができます。ジョブが正しく実行されればCircleCIとGitHubの連携は完了です。
ちなみにCircleCIがGitHubのプライベートリポジトリをチェックアウトできるのは、GitHubとCircleCIが連携した際にCircleCIがGitHubのリポジトリの"読み取り権限"を持つからです。具体的にはGitHubが公開鍵を持ち、CircleCIが秘密鍵を持ちます。GitHubの公開鍵は対象リポジトリの"Settings"→"Deploy keys"で確認できます。CircleCIの秘密鍵は"Settings"→"Projects"→"対象プロジェクトの右上にある歯車アイコン"→"Checkout SSH keys"で確認できます。
GitHubとCircleCIの連携が完了したのでCircleCIからGCRにDockerイメージをpushできるようにします。
CircleCIからGCRにDockerイメージをpushするためにはCircleCIのジョブでCLOUD SDKを利用する必要があります。そこで解決しなければならない問題がCircleCIからGCRへDockerイメージをpushする際の認証です。CircleCIのジョブ内でインストールしたCLOUD SDKに認証を設定する必要があります。CLOUD SDKの認証を設定するためのサンプルはこちらにあります。おそらくリスト2.28のようなジョブ定義だと思います。
ジョブを実行する"google/cloud-sdk"イメージにはCLOUD SDKが含まれているのでジョブで明示的にCLOUD SDKをインストールする必要はありません。"$GCLOUD_SERVICE_KEY"にはGCLOUD SDKの認証設定に利用する"サービスアカウント"という秘密鍵を指定します。CircleCIからDockerイメージをGCRへpushするためにはサービスアカウントを理解する必要があります。
GCPでは各リソースに対する権限管理の仕組みとしてCloudIAMというサービスが存在します。サービスアカウントはCloudIAMが提供する認証方法の1つです。エンドユーザーに対する権限管理ではなく、GCP上にホストされているリソース同士のアクセスや外部サービスからのアクセスの権限管理に利用します。ここの"CircleCIからGCRにDockerイメージをpushする"という要件は外部サービスからGCPへのアクセスなので、サービスアカウントによる認証を利用します。CircleCI以外の外部サービスとGCPを連携させる場合もサービスアカウントを利用することになります。例えば本書で扱うSpinnakerはGCPへアクセスする必要があるので、サービスアカウントを利用します。
サービスアカウントを作成します。リスト2.29のコマンドを実行してください。
作成したサービスアカウントはリスト2.30のコマンドで確認できます。
CloudIAMには"Role"と"Permisson"という概念が存在します。Permissonは各リソースに対する権限です。そしてRoleはPermissonの集合体です。RoleとPermissonの関係性はこちらの具体例を確認するとわかりやすいと思います。例えば"roles/androidmanagement.user"というRoleはリスト2.31にあるPermissonを持ちます。
1つのサービスアカウントに1つのPermissonのみ付与するというユースケースは少ないでしょう。おそらく複数のPermissonを付与するはずです。そのためサービスアカウントはRole単位で権限を持ちます。Roleは複数持つことが可能です。そしてサービスアカウントにはPermissonを直接付与することはできません。例えばリスト2.31の"serviceusage.quotas.get"という1つのPermissonをサービスアカウントに付与することはできません。
しかし、CloudIAMにはCustomRoleという任意のPermissonを組み合わせたRoleを作成する仕組みがあります。そのため"serviceusage.quotas.get"という1つのPermissonだけを持つCustomRoleを作成し、サービスアカウントに付与することで、サービスアカウントに"serviceusage.quotas.get"のPermissonのみ持たせることが可能です。
サービスアカウントに付与されているRoleはリスト2.32のコマンドで確認できます。サービスアカウントを作成するだけではRoleが付与されないので"etag"のみ表示されるはずです。
さきほど作成したサービスアカウントにGCRへpushするためのRoleを付与しましょう。少しややこしいのですが、実はGCR用のRoleというものは存在しません。GCRに対する権限管理はGCS(Google Cloud Storage)のRoleを利用します。GCRへDockerイメージをpushするには"roles/storage.admin"というRoleが必要です。リスト2.33のコマンドを実行してください。
リスト2.34のコマンドで付与されたRoleを確認することができます。
サービスアカウントをCircleCIに登録します。CircleCIに登録するサービスアカウントの実体はJSON形式の秘密鍵です。そのためCircleCIには環境変数としてJSON文字列を登録します。秘密鍵はリスト2.35のコマンドで作成することができます。
CircleCIに秘密鍵を環境変数として登録しましょう。CircleCIの左メニュー"SETTINGS"→Projectsにて対象リポジトリの右上の歯車アイコンをクリックします。(図2.6)
設定ページの"Environment Valiables"の"Add Valiable"をクリックします。(図2.7)
図2.8のようなフォームが表示されるので、"Name"にジョブの環境変数名である"GCLOUD_SERVICE_KEY"を入力し、"Value"に秘密鍵(JSON文字列)をそのまま入力します。
CircleCIではこのように環境変数を登録することでジョブから参照することができます。
リスト2.28を参考にGCRへpushするジョブを書くとリスト2.36になります。
"$GCLOUD_SERVICE_KEY"は環境変数として登録したサービスアカウント(JSON文字列)になります。"gcloud auth activate-service-account --key-file=-"はCLOUD SDKの認証にサービスアカウントを利用するための設定です。CLOUD SDKの認証にサービスアカウントを利用するという方法は自社のオンプレサーバやGCP以外のクラウドプラットフォームのサーバからGCPにアクセスする際に便利なので覚えておくと良いでしょう。
"setup_remote_docker"はCircleCI上でdockerコマンド、docker-composeコマンドを利用する際に設定します。"setup_remote_docker"で指定するDockerの"version"についてはこちらに載っています。安定版のバージョンはこちらから確認できますが、CircleCIで最新のバージョンを利用できるとは限りません。サポート外のversionを指定した場合、ジョブ実行時にリスト2.37のエラーが発生します。
ここではドキュメント上のサンプルで指定している"18.06.0-ce"を指定します。(図2.9)
"gcloud --quiet auth configure-docker"はDockerの"Credential helpers"を利用するためのコマンドです。Credential helpersはGCRのような特定のDocker Registry向けの認証を"docker login"コマンドを利用せずに提供する仕組みです。AWSにも"Amazon ECR Docker Credential Helper"というCredential helpersが存在します。GCPの場合はサービスアカウントによって設定したCLOUD SDKの認証情報を利用して、"docker push"コマンドにてGCRにDockerイメージをpushすることができるようになります。Dockerイメージをpushする方法は他にもあるので興味のある方は確認してみてください。
"name: Push docker image"はdockerコマンドを利用してDockerイメージをpushしているだけなので説明は省略します。
CircleCIからGCRにDockerイメージをpushするためにGCRのAPIを有効にします。リスト2.38のコマンドを実行してください。
ちなみにGCRのAPIを有効にする前にCLOUD SDKからGCRを操作するとリスト2.39のようなエラーが発生します。
有効になっているAPIはリスト2.40で確認することができます。
GCRのAPIを有効にしたらリスト2.36のジョブ定義を".circleci/config.yml"に上書きしてmaster branchにpushしましょう。CircleCIでビルドが走り、GCRにイメージがpushされるはずです。
GCRにpushされたDockerイメージはリスト2.41のコマンドで確認できます。
指定したイメージのタグはリスト2.42のコマンドで確認できます。
これでDockerイメージをGCRにpushすることができました。
GCR上のDockerイメージをGKEにデプロイするためにSpinnakerを利用します。SpinnakerはCD(Continuous Delivery)を実現するOSSです。ソフトウェアをデプロイする際に必要となる機能を備えているだけではなく、マルチクラウドに対応しているという特徴を持っています。つまり、本書で扱うGKEに対するデプロイだけではなく、AWS。Spinnakerを利用するメリットについてはこちらの記事が参考になります。
Spinnakerを構築する前に第4章「Spinnaker Tips」の「4.9 Spinnakerトラブルシューティング」に目を通しておくことをおすすめします。
Spinnakerのインストール方法には大きく分けて2つあります。1つ目はクイックスタートです。クイックスタートにも複数のインストール方法があるのですが、GCPであれば"Google Cloud Launcher"でインストールすることが可能です。2つ目は"Halyard"というSpinnaker専用のコマンドラインツールを利用する方法です。
Spinnakerのドキュメント上にはクイックスタートは本番環境で利用するものではないという記載があるので、実際に業務で利用する際にはHalyardを利用して構築することになります。そのためここでは"Halyard"を利用する通常の構築方法でSpinnakerを構築します。Halyardを利用したSpinnakerの構築方法はこちらです。ここではこれを参考にSpinnakerの構築を進めます。