経緯

本技術ブログはWordpressを使い、GCPで運用しています。
構築の際、後々見た目を変更するためにテーマのソースファイルをいじることもあるだろうと思い、テーマ関連ソースはCloud Source Repositoriesで管理することにしました。
その際、ソースを編集したあとのデプロイの手間は省きたいと考え、「Cloud Source Repositoriesに作成したGitリポジトリのmasterブランチにプッシュされたら、Wordpressが起動しているGoogle Compute EngineのVMインスタンスに対してデプロイを実行する」というCI/CDを構築しました。

備忘録も兼ねて、その構築手順を記事にすることにします。
※途中のスクリーンショットで一部公開できない内容(IDやプロジェクト名など)は伏せさせていただいております。

手順

参考:WordPressのデプロイを楽にしたい! Cloud Build導入のすすめ
上記のページを参考に進めていきます。

今回のCI/CDを構築するにあたり、利用するGCPのサービスは以下です。

  • Google Compute Engine
  • Cloud Source Repositories
  • Cloud KMS
  • Cloud Build

Cloud KMSはデータの暗号化/復号化のサービスです。
今回、GCEのVMインスタンスにSSH接続する際の秘密鍵を生データのまま扱いたくないため、このサービスを利用しています。
Cloud BuildはGCP上で実行されるCI/CDツールです。

Google Compute Engineのマシンはe2-microを使用、静的IPを割り当て、ドメイン設定済み。
Wordpressのページが見れる状態となっています。

Cloud Source Repositoriesには、リポジトリを作成し、masterブランチをきって、Wordpressで使用しているテーマのソースファイルを一式保存してあります。
(WordPressのファイル構成でいうと、wp-content/themes 配下)

Google Compute Engineへのデプロイは以下のようにして行う想定です。

  • Source RepositoriesへのプッシュをトリガーにCI/CD(Cloud Build)実行
  • Cloud BuildからGoogle Compute EngineのVMインスタンスにSSH接続
  • rsyncを使用してSource RepositoriesのソースをVMインスタンスのソースに反映

また、今回の実装を行うにあたり、ローカルPCにCloud SDKが必要なのでインストールしておきましょう。
参考:GCPのgcloudコマンドのインストールと最初の認証までを初心者向けに細かく解説

では、上記を実行すべく、CI/CDを構築していきます。

1. Cloud KMSを使用して暗号化/ 復号化のキーを生成する
Cloud KMSでは「キーリング」と「キー」を作成します。
「キーリング」とは、鍵を束ねるキーホルダーのようなものです。
プロジェクト毎にキーリングを作成し、暗号化/復号化のための複数のキーをキーリングで管理する、というイメージで運用するとよいと思います。

ではまず、GCPで「セキュリティ」→「鍵管理」ページに遷移します。

遷移したら「キーリングの作成」を押下します。

キーリング名を入力し「作成」を押下します。

次に「キー」を作成します。

「鍵名」を入力し「作成」を押下します。
これでキーリングとキーは作成されました。

注意点:
作成したキーリング、および、キーは削除することができません。
使用しないキーを何個も作ってしまうと削除することができないので非常に煩雑化します。
不要なキーリング、および、キーは作成しないようにしましょう。
参考:Cloud KMS (Cloud Key Management Service) について調べたことのまとめ

では、作成したキーを用いて、VMインスタンスにSSH接続するための秘密鍵を暗号化します。
ターミナルを使用して以下のコマンドを実行します。

cat <SSH秘密鍵のパス> | \
gcloud --project <GCPプロジェクトID> kms encrypt \
--plaintext-file=- \
--ciphertext-file=- \
--location=global \
--keyring=<キーリング名> \
--key=<キー名> | base64

標準出力に暗号化された秘密鍵が出力されるので、適当なテキストファイルにコピペしておいてください。
後で使用します。

2. CI/CDを実行するためのトリガーを作成する
次にCloud BuildにCI/CD実行のトリガーを作成します。
今回のCI/CDのトリガーは「Cloud Source Repositoriesのmasterブランチにプッシュされたら」とします。

「Cloud Build」→「トリガー」に遷移し「トリガーの作成」を押下します。

以下の項目を入力します。
名前(必須):任意の名称
説明(任意):トリガーの説明
イベント:ブランチにpushする
ソース:プッシュ対象のリポジトリ
ブランチ:^master$

構成

  • 形式:Cloud Build構成ファイル(yaml または json)
  • ロケーション:リポジトリ
  • Cloud Build構成ファイルの場所:/cloudbuild.yaml

上記項目の下「詳細設定」の「代入変数」も設定していきます。
後ほど、ビルドの内容をcloudbuild.yamlファイルで定義していきますが、ここの「代入変数」で設定した値は、cloudbuild.yamlファイルで使用することができます。
サーバーの情報などはGit管理したくないので、ここに値を持たせておくことにします。
代入変数は以下のように設定します。

変数
_ENC 手順1で暗号化しbase64エンコードした秘密鍵。
_KEYNAME 手順1で作成したキーの名称。
_KEYRING 手順1で作成したキーリングの名称。
_PORT SSH接続時のポート。デフォルトは22。
_SSH SSH接続する際のユーザー名、ホスト、接続先ディレクトリ。
(例)<SSHユーザー名>@xxx.xxx.xxx.xxx:/var/www/html/wp-content

設定したら「保存」を押下してトリガーを保存します。

3. Cloud Buildのサービスアカウントに権限を付与する
Cloud Buildを実行するサービスアカウントが、他のサービスを実行できるよう、権限を付与する必要があります。
今回は「Compute Engine」と「Cloud KMS」を実行するため、この2つのステータスを「有効」にします。

4. cloudbuild.yamlの作成
次にCI/CDの内容を記述したyamlファイルを作成します。
今回のyamlファイルの保存場所は「Gitリポジトリのルートディレクトリ」としています。
yamlファイルの配置場所を変更したい場合は、手順2の「Cloud Build構成ファイルの場所」を変えることで対応できます。
下記の内容を記述し、masterブランチへプッシュします。
※cloudbuild.yaml

steps:
  - name: "gcr.io/cloud-builders/gcloud"
    entrypoint: bash
    args:
      - "-c"
      - |
        echo "$_ENC" | base64 -d >/workspace/decrypt-base64
  - name: "gcr.io/cloud-builders/gcloud"
    args:
      - kms
      - decrypt
      - --ciphertext-file=/workspace/decrypt-base64
      - --plaintext-file=/workspace/ssh-key
      - --location=global
      - --keyring=$_KEYRING
      - --key=$_KEYNAME
  - name: "gcr.io/cloud-builders/gcloud"
    entrypoint: bash
    args:
      - "-c"
      - |
        chmod 0600 /workspace/ssh-key
  - name: "gcr.io/cloud-builders/gcloud"
    entrypoint: bash
    args:
      - "-c"
      - |
        apt-get update
        apt-get -y install rsync
        rsync -rcvzOP --rsync-path="sudo rsync" --inplace --no-inc-recursive -e "ssh -o 'StrictHostKeyChecking no' -p ${_PORT} -i /workspace/ssh-key" /workspace/wp-content/ ${_SSH}

ファイルに記述された処理を1つずつ説明します。
まず、トリガー作成時に設定した代入変数「_ENC」の内容(Cloud KMSで暗号化された後base64でエンコードされた秘密鍵)をbase64でデコードした後、「decrypt-base64」というファイルに書き出します。

  - name: "gcr.io/cloud-builders/gcloud"
    entrypoint: bash
    args:
      - "-c"
      - |
        echo "$_ENC" | base64 -d >/workspace/decrypt-base64

次に「decrypt-base64」に書き出された内容をCloud KMSで復号化し、「ssh-key」というファイルに書き出します。

  - name: "gcr.io/cloud-builders/gcloud"
    args:
      - kms
      - decrypt
      - --ciphertext-file=/workspace/decrypt-base64
      - --plaintext-file=/workspace/ssh-key
      - --location=global
      - --keyring=$_KEYRING
      - --key=$_KEYNAME

その後、作成した「ssh-key」ファイルの権限を「所有者のみ読み書き可」に変更します。

  - name: "gcr.io/cloud-builders/gcloud"
    entrypoint: bash
    args:
      - "-c"
      - |
        chmod 0600 /workspace/ssh-key

最後にGoogle Compute EngineのVMインスタンスに復号化した秘密鍵を用いてSSH接続し、rsyncを使用してプッシュされたファイルをデプロイします。

  - name: "gcr.io/cloud-builders/gcloud"
    entrypoint: bash
    args:
      - "-c"
      - |
        apt-get update
        apt-get -y install rsync
        rsync -rcvzOP --rsync-path="sudo rsync" --inplace --no-inc-recursive -e "ssh -o 'StrictHostKeyChecking no' -p ${_PORT} -i /workspace/ssh-key" /workspace/wp-content/ ${_SSH}

これで、masterブランチへのプッシュをトリガーに、CI/CDが実行され、プッシュされたファイルがデプロイされるようになります。
試しに、ファイルを1つ適当に修正し、masterブランチにプッシュをすると、CI/CDが実行されました。

これでCloud Souece RepositoriesへのプッシュをトリガーにCloud Buildを使ってデプロイする処理の実装完了です。

お疲れ様でした。

注意点

自分が実装したとき、実はいくつかの点でなかなかうまくいかず、かなり苦労しました。
その詰まった点について、以下に記載していきます。

秘密鍵にパスワードを付与しているとrsyncのSSHログインに失敗する

私はVMインスタンスに対してファイル転送を行うため、FTPソフトのFilezillaを使用していました。
FilezillaでSFTP通信を行う場合は、SSH接続に使用する秘密鍵にパスワードをかける必要があり、そのように秘密鍵を生成していました。
パスワードをかけた秘密鍵を使ってSSH接続を行おうとすると、本来であれば対話式でパスワードの入力が求められます。

しかし、今回、SSH接続はrsyncを介して行うため、パスワードの入力ができず、permission denied (public key)でSSH接続に失敗していました。

今回は、パスワード設定なしの公開鍵を新たに作成し、それをVMインスタンスに追加で設定し、その秘密鍵を用いてSSHログインすることで、上記現象を回避しました。

rsync: failed to set times on "xxxxxxx": Operation not permitted (1)で失敗する

SSHのパスワード問題が解決し、実際に処理が開始するようになったら、今度はOperation not permittedエラーが発生しました。

元々、rsyncのコマンドは以下のように指定していました。

rsync -acvzOP --inplace --no-inc-recursive -e "ssh -o 'StrictHostKeyChecking no' -p ${_PORT} -i /workspace/ssh-key" /workspace/wp-content/ ${_SSH}

ここで指定していた -a オプションのパーミッション保持やタイムスタンプ保持がSSHユーザーに認められておらず、ファイル操作が許可されなかったのが原因でした。
(rsyncのオプションの説明については右記参照。参考:【 rsync 】コマンド(その1)――ファイルやディレクトリを同期する)

不要なオプションを落とし、以下のコマンドとすることで上記現象を回避しました。

rsync -rcvzOP --inplace --no-inc-recursive -e "ssh -o 'StrictHostKeyChecking no' -p ${_PORT} -i /workspace/ssh-key" /workspace/wp-content/ ${_SSH}

rsync: open "/var/www/html/wp-content/themes/intimate/xxxxxxx" failed: Permission denied (13)で失敗する

タイムスタンプ保持やパーミッション保持を回避するようにすると、今度はPermission deniedエラーが発生しました。

所有者権限で実行するrsyncはファイルコピー等は認められないことが原因です。
これはrsyncそのものをroot権限で実行するように指定して回避しました。

rsync -rcvzOP --rsync-path="sudo rsync" --inplace --no-inc-recursive -e "ssh -o 'StrictHostKeyChecking no' -p ${_PORT} -i /workspace/ssh-key" /workspace/wp-content/ ${_SSH}

「--rsync-path="sudo rsync"」のオプションを付与することで、rsyncがroot権限で実行されるようになり、ファイルコピー等が行えるようになります。

参考

WordPressのデプロイを楽にしたい! Cloud Build導入のすすめ
感謝の気持ちをCloud KMSで暗号化して送ってみた
rsync でディレクトリの同期(バックアップ)
【 rsync 】コマンド(その1)――ファイルやディレクトリを同期する
root権限でrsyncを実行する

プロフィール

窪田将太
窪田将太
制御系エンジニア、Webエンジニアを経て現在DevOpsへ。
元々エンジニアではなく、異業種転職組。
現在、コンテナやクラウド(AWS、GCP)を鋭意勉強中。