Cloud Runとワークフロー

GCPの管理下の環境に、コンテナを乗せて動かすことができるサーバーレス環境です。
AWSのFargateと似たような立ち位置っぽいです。
https://cloud.google.com/run?hl=ja

Cloud Runの実行は、HTTPアクセスで行います。
これだけだと使い勝手が悪いので、ワークフローと組み合わせます。
https://cloud.google.com/workflows?hl=ja

これにより、GCPのコンソール画面からCloud Runが実行できるようになります。
今回はこれらを使って、Word Pressのテーマリリースをサーバレスで実行できるようにします。

なお、一連の作業には下記のチュートリアルが参考になります。
https://cloud.google.com/run/docs/triggering/using-workflows?hl=ja

事前準備

スキーム

まずはリリーススキームを考えます。

リリースするテーマは、GitHubで管理します。
これをCloud Runで取得し、WPサーバへ配置します。

Cloud Runの実行準備

Cloud Runについてはこちら参考にセットアップしました。
https://cloud.google.com/run/docs/quickstarts/build-and-deploy/deploy-python-service

GitHubトークンの取得・保管

今回はコマンドでGithubから資材をクローンするため、トークンを作成しておきます。
GitHubにログインし、「Settings」→「Developer settings」→「Personal access tokens」へ遷移します。
下記URLからも飛べます。
https://github.com/settings/tokens

「Generate new token」を押下し、
トークンの説明、有効期限、スコープを設定して「Generate token」を押下します。
スコープついて、今回はリポジトリをクローンするだけなので、「repo」だけチェックしておけば十分です。

有効期限が切れるとコンテナが実行できなくなるため、気をつけましょう。

作成されたトークンは後でGCPへアップロードするため、コピーしておきます。
ローカルでテストしたい場合には、ファイルとして保存しておきます。

生成したトークンを、Scret Managerにアップロードします。
https://console.cloud.google.com/security/secret-manager

「シークレットを作成」を選択し、先ほどコピーしたトークン値を設定して作成します。

これで、GitHubのトークンをGCPで利用できるようになりました。

Cloud Runのソースコード

Cloud Runにデプロイするソースコードはこれだけです。

  • Dockerfile
  • requirements.txt
  • app.py

Dockerfile

コンテナのインフラ設定が書かれているファイルです。

# Python image to use.
FROM python:3.10-alpine

# Set the working directory to /app
WORKDIR /app

# copy the requirements file used for dependencies
COPY requirements.txt .

# Install any needed packages specified in requirements.txt
RUN pip install --trusted-host pypi.python.org -r requirements.txt

# Install Git
RUN apk update \
    && apk add git

# Copy the rest of the working directory contents into the container at /app
COPY . .

# Enviroment Variables
ENV GITHUB_TOKEN_PATH /etc/token/github_token
ENV GITHUB_REPO_OWNER tktkjuntech
ENV GITHUB_REPO_NAME wordpress-themes
ENV WP_THEME_NAME original-theme
ENV WP_HOST XXXXXXXXXX
ENV WP_PORT 10022
ENV SFTP_USER autohacks
ENV SFTP_KEY_PATH /etc/ssh/id_rsa
ENV REMOTE_WORK_DIR /home/user/wordpress/public_html/wp-content/themes

# Run app.py when the container launches
ENTRYPOINT ["python", "app.py"]

リリーススクリプトをPythonで作成するため、Pythonのイメージをベースに作成しています。
コンテナ内でGitを利用するため、RUNコマンドでGitをインストールするよう記述。
Alpine Linuxのイメージをベースに作成しているため、yumapt-getが使えないのですが、
これに気づかず苦労しました。。
結果、apkでインストールするようにしています。

requirements.txt

pythonで使うライブラリを記載しています。
要するにpip installするやつです。

Flask==2.2.2
GitPython==3.1.27
paramiko==2.10.4

app.py

リリーススクリプトです。

import os
import git
import paramiko
import shutil

from flask import Flask

app = Flask(__name__)

@app.route("/")
def release_wp_theme():
    try:
        print("Get New Artifacts ...")
        with open(os.environ.get("GITHUB_TOKEN_PATH"), 'r', encoding='utf-8') as f:
            github_token = f.read().splitlines()[0]
        repo_owner = os.environ.get("GITHUB_REPO_OWNER")
        repo_name = os.environ.get("GITHUB_REPO_NAME")
        artifacts_path = './artifacts'
        theme_name = os.environ.get("WP_THEME_NAME")
        # Init
        os.system(f"rm -rf {artifacts_path}")
        os.system(f"rm -rf ./{theme_name}.zip")
        #
        git_clone_url = f'https://{github_token}@github.com/{repo_owner}/{repo_name}.git'
        cloned_repo = git.Repo.clone_from(git_clone_url,artifacts_path)
        #
        # ZIP作成
        print("Archive Artifacts ...")
        archive_target_path = f"{artifacts_path}/{theme_name}/"
        shutil.make_archive(theme_name, 'zip', root_dir=archive_target_path)
        #
        # SSH接続
        print("Create New Connection ...")
        sftp_host = os.environ.get("WP_HOST")
        sftp_port = os.environ.get("WP_PORT")
        sftp_user = os.environ.get("SFTP_USER")
        sftp_key_file = os.environ.get("SFTP_KEY_PATH")
        rsa_key = paramiko.RSAKey.from_private_key_file(sftp_key_file)
        #
        client = paramiko.SSHClient()
        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        client.connect(sftp_host, port=sftp_port, username=sftp_user, pkey=rsa_key)
        sftp_con = client.open_sftp()
        #
        # Zipファイル送信
        print("Send New Artifacts ...")
        remote_work_dir = os.environ.get("REMOTE_WORK_DIR")
        remote_theme_dir = f"{remote_work_dir}/{theme_name}"
        remote_archive_path = f"{remote_work_dir}/{theme_name}.zip"
        sftp_con.put(f"./{theme_name}.zip",remote_archive_path)
        #
        # 既存のディレクトリ削除
        print("Remove Old Artifacts ...")
        client.exec_command(f"rm -rf {remote_theme_dir}")
        # ファイル配布
        print("Distribute New Artifacts ...")
        stdin, stdout, stderr = client.exec_command(f"unzip {remote_archive_path} -d {remote_theme_dir}")
        cmd_result = ''
        for line in stdout:
            cmd_result += line
        print(cmd_result)
        # ZIP削除
        print("Remove Archive File ...")
        sftp_con.remove(remote_archive_path)
        # Connection Close
        client.close()
        print("Complete!")
        #
        return "Success!"
    except Exception as e:
        return str(e)

if __name__ == '__main__':
    server_port = os.environ.get('PORT', '8080')
    app.run(debug=False, port=server_port, host='0.0.0.0')

Cloud Runで動かすスクリプトということで、
HTTPアクセスを受けて実行する実行する方式となっています。
GitHubからリリースする資材をリリースする資材を取得したのち、
SFTPでWPサーバへ配布します。
サーバには公開鍵認証で接続するため、秘密鍵をGitHubトークンと同じ要領でSecret Managerへ保管しておきます。

デプロイからCloud Run実行まで

リリーススクリプトが存在するディレクトリで、gcloud run deployを実行します。
後々テスト実行したいため、Allow unauthenticated invocations...yにしておきます。

$ gcloud run deploy --source .
Service name (release-wp-theme):
This command is equivalent to running `gcloud builds submit --tag [IMAGE] .` and `gcloud run deploy release-wp-theme --image [IMAGE]`

Allow unauthenticated invocations to [release-wp-theme] (y/N)?  y

完了するとService URL:にデプロイ先のURLが表示されるので、ブラウザでアクセスしてみます。
すると、下記エラーが画面に返却されます。

指定されたディレクトリにファイルがないよ、と怒られています。
ここからは、コンソール画面で設定を続けます。
https://console.cloud.google.com/run

GCPのコンソール画面に行くと、先ほどデプロイしたサービスが表示されています。

これを選択し、「新しいリビジョンの編集とデプロイ」を選択します。

編集画面をスクロールしていくと、「シークレット」という項目があります。
ここで、Secret Managerに保管してあるGitHubトークンとWPサーバ接続用の秘密鍵をボリュームとしてマウントします。

マウントパスは、Dockerfile内でENVに記述しているものと合わせます。

これでデプロイし、再度コンテナのURLへアクセスすると、

無事「Success!」が返ってきて、リリースすることができました。

ワークフローへの接続

ここからは、デプロイしたCloud Runサービスをワークフローで実行できるようにします。

現状だとインターネット上から誰でもリリースできるようになっているため、
まずはCloud Runでアクセス制限を行います。
デプロイしたサービスを選択し、トリガーから設定を下記へ変更します。

保存後、再度コンテナのURLへアクセスすると、

403エラーが返ってくるようになりました。

ここから、ワークフローでCloud Runを実行できるようにします。
まずは下記を参考に、サービスアカウントを作成します。
https://cloud.google.com/run/docs/triggering/using-workflows?hl=ja

サービスアカウントが作成できたら、ワークフローを作成します。

サービスアカウントには、先ほど作成したものを指定します。


ワークフローの定義は下記です。

# This is a sample workflow to test or replace with your source code.
#
# This workflow passes the current day of the week to the Wikipedia API and
# returns a list of related Wikipedia articles.
# The current day of the week (in GMT) is retrieved from a Cloud Function
# unless you input your own search term (for example, {"searchTerm": "Monday"}).
main:
    params: [input]
    steps:
    - exeCroudRun:
        call: http.get
        args:
            url: https://XXXXX.a.run.app
            auth:
                type: OIDC
        result: output
    - returnOutput:
            return: ${output}

先ほど手動アクセスしたURLへアクセスするだけの、簡単なフローとなっています。
これをデプロイし、実行してみます。



無事、ステータス200で「Success!」が返ってきました。