GitHub和GCP结合,形成CI/CD等服务的架构
首先
我是湊(みなと)(@karura618),在株式会社Trust Bank的服务开发部门工作。
本文是关于Trust Bank 2023年的Advent Calender活动的文章。
这次我们使用GitHub + GCP的CloudBuild、ArtifactRegistry和CloudRun来引入了一个最小化的CI/CD服务。由于每个服务的设置都非常简单,所以本次不进行详细介绍,我打算解释一下实际创建的配置文件。
服务简介
由于长度较长,已收起以节省空间。
GitHub是一个用于共享和管理程序源代码的服务。
它可以与后续提到的CloudBuild进行集成,通过分支名称来控制只运行单元测试,或进行部署等操作。
网址:https://github.co.jp/
它可以与后续提到的CloudBuild进行集成,通过分支名称来控制只运行单元测试,或进行部署等操作。
网址:https://github.co.jp/
CloudBuild 是 Google Cloud Platform(GCP)的一项服务,可以轻松实现持续集成和持续部署(CI/CD)。通过在创建触发器时与上述 GitHub 存储库进行集成,可以轻松引入 CI/CD。在已集成的 GitHub 存储库中放置名为 “cloudbuild.yaml” 的 CI/CD 配置文件,即可在推送等操作时执行 “cloudbuild.yaml” 中的处理。此外,可以在触发器上定义 “分配变量”。定义的分配变量可以在 “cloudbuild.yaml” 中进行传递,因此可以很方便地根据要公开的服务调整镜像、服务名称、区域等希望调整的值。顺便提一下,如果要通过 CloudBuild 与 GitHub 存储库进行集成,则需要将触发器创建时的 GitHub 帐户与其关联,因此如果使用个人帐户创建,请注意。一旦不小心删除帐户,则无法运行。此外,在进行协作时,GitHub 帐户必须具有对目标存储库的管理员权限。另外,尽管本次未使用,但可以在 “cloudbuild.yaml” 中引用 “Secret Manager”,因此建议将密码、证书等安全信息在那里进行管理。
ArtifactRegistry是AWS中的ECR(Elastic Container Registry)。
它是用于存储和访问容器镜像的仓库(或仓库集合)。
大约两年前,ContainerRegistry曾是主流,但现在已被弃用,并且推荐将由ContainerRegistry创建的镜像迁移到ArtifactRegistry。
在使用ContainerRegistry时,不需要事先准备仓库,但在使用ArtifactRegistry时,似乎需要在运行CloudBuild之前事先准备存储仓库,个人感觉有点麻烦。
https://cloud.google.com/artifact-registry?hl=ja
它是用于存储和访问容器镜像的仓库(或仓库集合)。
大约两年前,ContainerRegistry曾是主流,但现在已被弃用,并且推荐将由ContainerRegistry创建的镜像迁移到ArtifactRegistry。
在使用ContainerRegistry时,不需要事先准备仓库,但在使用ArtifactRegistry时,似乎需要在运行CloudBuild之前事先准备存储仓库,个人感觉有点麻烦。
https://cloud.google.com/artifact-registry?hl=ja
CloudRun是一个全托管的计算平台,可以自动缩放容器。
由于是无服务器的,所以您可以在没有基础设施知识的情况下构建服务,只需要部署即可。
但是,您需要准备一个Dockerfile,并且需要在一个容器中配置应用程序和Web服务器(例如,需要在一个容器中安装PHP和Nginx)。
因此,Dockerfile的配置可能有点复杂,有点不像习惯在docker compose之类的工具中将应用程序和Web明确分开配置,但习惯后就不会那么在意了。
看起来还支持多容器。
还支持自动缩放,只有当CPU使用率超过60%时才会进行横向扩展。
https://cloud.google.com/run?hl=ja
由于是无服务器的,所以您可以在没有基础设施知识的情况下构建服务,只需要部署即可。
但是,您需要准备一个Dockerfile,并且需要在一个容器中配置应用程序和Web服务器(例如,需要在一个容器中安装PHP和Nginx)。
因此,Dockerfile的配置可能有点复杂,有点不像习惯在docker compose之类的工具中将应用程序和Web明确分开配置,但习惯后就不会那么在意了。
看起来还支持多容器。
还支持自动缩放,只有当CPU使用率超过60%时才会进行横向扩展。
https://cloud.google.com/run?hl=ja
构建形象
文件结构
使用PHP(Laravel)进行配置。
C:
│ artisan
│ cloudbuild.yaml // CloudBuild構成ファイル(CloudRunへのデプロイ用)
│ cloudbuildtest.yaml // CloudBuild構成ファイル(作業ブランチへのプッシュ時にユニットテストを実施)
│ composer.json
│ composer.lock
│ docker-compose.yml
│ phpstan.neon // 静的解析ツールの設定ファイル(今回は説明割愛)
│ phpstan_ergebnis.neon // 静的解析ツールの拡張設定ファイル(今回は説明割愛)
│ phpunit.xml
│ ruleset.xml // lint、フォーマッターの設定ファイル(今回は説明割愛)
│
├─app
│
├─docker
│ │ docker-php-ext-xdebug.ini
│ │ Dockerfile // cloudbuild.yamlを同じ階層に置かない場合、ちょっと対応が必要(後述)
│ │ entrypoint.sh
│ │ php-fpm.conf
│ │ php.ini
│ │ startup.sh
│ │
│ ├─nginx
│ │ nginx.conf
│ │ php.conf
│ │
│ └─php-fpm.d
│ www.conf
│
│
├─public
│
├─resources
│
├─routes
│ │ api.php
│ │ channels.php
│ │ console.php
│ └─web.php
│
├─tests
│ │ CreatesApplication.php
│ │ TestCase.php
│ │
│ ├─Feature
│ │ ExampleTest.php
│ │
│ └─Unit
│ ExampleTest.php
│
└─vendor
云构建文件。
steps:
# Build the container image
# 以下はCloudBuildの代入変数への登録が必須な項目です。(先頭の「$」は除外して登録)
# $_IMAGE_NAME : ArtifactRegistryに登録されるイメージ名及び、CloudRunのサービス名称
# $_REGION_NAME : ArtifactRegistryの保管場所となるリージョン指定。デフォルトは「asia-northeast1」にしているので特に理由がなければ設定不要
# $_DOCKERFILE_MULTI_STAGE_NAME : ビルド時に使用するDockerfile内のマルチステージ名を指定。テスト環境であれば「development」。本番は「production」
# CloudBuildで使用するDockerfileはデフォルトだとcloudbuild.yamlと同一階層に配置する必要がある。
# 管理上の理由等によりサブディレクトリ内にDockerfileを配置したい場合は「-f」オプションを使用してファイルパスを記述する必要がある。
# CloudBuild内のビルド時に使用するディレクトリは「/workspace」となるため以下のように「/workspace/docker/Dockerfile」を指定することで対応可能。
# (1)docker/Dockerfileを使用してビルドを実施。「-t」オプションなしの場合はcloudbuild.yamlと同じ階層にあるDockerfileを使用
# Docker 17.05からマルチステージビルドにも対応しており「--target」でステージの指定が可能。
- name: 'gcr.io/cloud-builders/docker'
args:
- 'build'
- '-t'
- '${_REGION_NAME}-docker.pkg.dev/$PROJECT_ID/$REPO_NAME/$_IMAGE_NAME:latest'
- '-f'
- '/workspace/docker/Dockerfile'
- '--network=cloudbuild'
- '--target=$_DOCKERFILE_MULTI_STAGE_NAME'
- '.'
id: build-container-image
# (2)(1)でビルドしたイメージをArtifactRegistryへ格納(プッシュ)する。
# 実施前にArtifactRegistry側で対象のリポジトリを作成する必要がある。
- name: 'gcr.io/cloud-builders/docker'
args: ['push', '${_REGION_NAME}-docker.pkg.dev/$PROJECT_ID/$REPO_NAME/$_IMAGE_NAME:latest']
# (3)(2)でプッシュしたイメージをもとにCloudRunへのデプロイを実施する。
# イメージ名をそのままCloudRunのサービス名として使用。
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: gcloud
args:
- 'run'
- 'deploy'
- '$_IMAGE_NAME'
- '--project'
- $PROJECT_ID
- '--image'
- '${_REGION_NAME}-docker.pkg.dev/$PROJECT_ID/$REPO_NAME/$_IMAGE_NAME:latest'
- '--region'
- '$_REGION_NAME'
- '--platform'
- 'managed'
images:
- '${_REGION_NAME}-docker.pkg.dev/$PROJECT_ID/$REPO_NAME/$_IMAGE_NAME:latest'
substitutions:
# ArtifactRegistryに作成したリポジトリのリージョンを指定。
# 特に理由がなければ東京リージョンを使用するように初期値をセット
# CloudBuildのトリガーで初期値がセットされていれば指定したリージョンを使用。
_REGION_NAME: "asia-northeast1"
# ビルド時のマルチステージを指定。デフォルトは「development」
_DOCKERFILE_MULTI_STAGE_NAME: "development"
# ビルド時のタイムアウト値をセット。(デフォルトは10分)
timeout: 1800s
# 今回は実装を省いていますがトリガーの履歴に対して以下のような独自タグをつけておくとPUB/SUBを使用したSlack通知とかも実装しやすいかもです。
tags: ['slack-notifier', $TRIGGER_NAME, $SHORT_SHA]
点数
-
- 汎用的な設定ファイルを構築したかったのでCloudBuildでデフォルトで用意されている変数を使用しています。
-
- デフォルトの変数で足りない箇所やデフォルトの変数が使えない箇所(※)については極力「substitutions」でデフォルト値を指定した上で個別に定義。
-
- ArtifactRegistryのイメージ名とCloudRunのサービス名はサービスごとに個別設定が必要になるのでデフォルト値は用意せずトリガーの代入変数で登録。
-
- 特に名称を分ける必要もなかったので「ArtifactRegistryのイメージ名」=「CloudRunのサービス名」としています。
- Dockerfileを含むDocker周りの設定ファイルは管理の都合上すべて「/dokcer」以下に配置したかったのでビルド時にDockerfileの場所を明示的に指定するよう「-f」オプションを追加。
由于CloudBuild的默认区域是”global”,而ArtifactRegistry中不存在该区域,因此我们没有使用CloudBuild的默认变量”LOCATION”来替代”_REGION_NAME”。
cloudbuildtest.yaml 的中文释义
steps:
# Build the container image
- name: 'gcr.io/cloud-builders/docker'
args:
- 'build'
- '-t'
- '${_REGION_NAME}-docker.pkg.dev/$PROJECT_ID/$REPO_NAME/unittest:$COMMIT_SHA'
- '--build-arg=_UNIT_TEST_ENV=$_UNIT_TEST_ENV'
- '-f'
- '/workspace/docker/Dockerfile'
- '--network=cloudbuild'
- '--target=$_DOCKERFILE_MULTI_STAGE_NAME'
- '.'
# MySQL(CloudSQL)を使用したユニットテストを実施する場合、CloudSQL用のProxyを用意しておく。
# CloudSql Proxy Standby
# - name: gcr.io/cloud-builders/docker
# args:
# - '-c'
# - >-
# docker run --network=cloudbuild
# -d -v /cloudsql:/cloudsql
# gcr.io/cloudsql-docker/gce-proxy:1.16 /cloud_sql_proxy -dir=/cloudsql
# -instances=$PROJECT_ID:${_REGION_NAME}:${_CLOUD_SQL_INSTANCE_NAME}
# id: proxy-cloud-sql
# entrypoint: bash
# Run phpstan
- name: gcr.io/cloud-builders/docker
args:
- '-c'
- >-
docker run --network=cloudbuild
${_REGION_NAME}-docker.pkg.dev/$PROJECT_ID/$REPO_NAME/unittest:$COMMIT_SHA sh -c "cd /var/www/html&&composer phpstan"
id: run-phpstan
entrypoint: bash
# Run phpstan
- name: gcr.io/cloud-builders/docker
args:
- '-c'
- >-
docker run --network=cloudbuild
${_REGION_NAME}-docker.pkg.dev/$PROJECT_ID/$REPO_NAME/unittest:$COMMIT_SHA sh -c "cd /var/www/html&&composer lint ./"
id: run-lint
entrypoint: bash
# Run UnitTest
- name: gcr.io/cloud-builders/docker
args:
- '-c'
- >-
docker run --network=cloudbuild
${_REGION_NAME}-docker.pkg.dev/$PROJECT_ID/$REPO_NAME/unittest:$COMMIT_SHA sh -c "cd /var/www/html&&php artisan test"
# MySQL(CloudSQL)を使用したユニットテストを実行する場合、以下のようにあらかじめ作成したProxyをコンテナ内にマウントしてテストを実施する。
# docker run -v /cloudsql:/cloudsql --network=cloudbuild
# ${_REGION_NAME}-docker.pkg.dev/$PROJECT_ID/$REPO_NAME/unittest:$COMMIT_SHA sh -c "cd /var/www/html&&php artisan test"
id: run-unittest
# waitFor:
# - proxy-cloud-sql
entrypoint: bash
images:
- '${_REGION_NAME}-docker.pkg.dev/$PROJECT_ID/$REPO_NAME/unittest:$COMMIT_SHA'
substitutions:
# ArtifactRegistryに作成したリポジトリのリージョンを指定。
# 特に理由がなければ東京リージョンを使用するように初期値をセット
# CloudBuildのトリガーで初期値がセットされていれば指定したリージョンを使用。
_REGION_NAME: "asia-northeast1"
# ユニットテスト実行時に必要な環境変数を指定(改行で複数指定可能)
_UNIT_TEST_ENV: ""
# ビルド時のマルチステージを指定。デフォルトは「development」
_DOCKERFILE_MULTI_STAGE_NAME: "development"
# _CLOUD_SQL_INSTANCE_NAME: $_CLOUD_SQL_INSTANCE
timeout: 1800s
# 今回は実装を省いていますがトリガーの履歴に対して以下のような独自タグをつけておくとPUB/SUBを使用したSlack通知とかも実装しやすいかもです。
tags: ['slack-notifier', $TRIGGER_NAME, $SHORT_SHA]
积分 (jī
-
- コンテナビルド時に「–build-arg」を使用してCloudBuildで設定した「_UNIT_TEST_ENV」の値をDockerfile内に渡しています。後述のDockerfile内でこの値を.env.testingファイルとしてコンテナ内に出力することでユニットテストに必要な環境変数をセットしています。
-
- (ユニットテスト用の環境変数なのでこの手法取っていますが本番環境で必要な環境変数をこの手法で配置するのはセキュアじゃないのでやめましょう;)
-
- 今回はコメントアウトしていますがユニットテスト時にDB(CloudSQL)を使用する場合はProxyを使用した接続が必要です。
-
- そのため、まずProxy用のコンテナを立ち上げた後、ユニットテストを実施するコンテナにマウントをしています。
- phpstan、lint、ユニットテストを実行するため各ステップで「-c」オプションを使用してコンテナ起動⇒composer、artisanを実施しています。また、コンテナ内の初期ディレクトリは「workspace」となるため「cd」でcomposer、artisanがある階層に移動しています。
Dockerfile (您只需要一個選項)
FROM php:fpm-alpine3.15 as base
WORKDIR /usr/src/php/ext
RUN set -eux; \
mkdir -p "$PHP_INI_DIR/conf.d"
# apkを最新に更新(セキュリティーアップデート含む)
RUN apk -U upgrade
RUN apk add autoconf
RUN apk add build-base
RUN set -x \
&& apk update \
&& docker-php-source extract \
&& apk add --no-cache git \
vim \
wget \
bash \
perl \
automake \
cmake \
build-base \
nginx \
libmcrypt-dev \
libpng-dev \
libxml2-dev \
freetype-dev \
libjpeg-turbo-dev \
unifont \
freetype \
harfbuzz \
ca-certificates \
ttf-freefont \
pcre-dev \
curl \
lsof \
zip \
rsync \
sudo
# timezone
RUN apk add --no-cache tzdata\
&& cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime \
&& echo "Asia/Tokyo" > /etc/timezone
RUN echo "www-data ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers \
&& echo 'www-data:www-data' | chpasswd
RUN git clone https://github.com/phpredis/phpredis.git redis \
&& docker-php-ext-install redis \
&& docker-php-ext-install mysqli \
&& docker-php-ext-install opcache \
&& docker-php-ext-install pdo \
&& docker-php-ext-install pdo_mysql \
&& docker-php-ext-install bcmath \
&& docker-php-ext-configure gd --with-jpeg \
&& docker-php-ext-install -j$(nproc) gd
# DockerHubにあるcomposerの公式イメージからcomposerをコピー
# https://hub.docker.com/_/composer
# 公式且つ、常に最新のcomposerを使用できるため、運用管理が楽。
# (以下issueでも推奨されている)
# https://github.com/docker-library/php/issues/344#issuecomment-364843883
COPY --from=composer /usr/bin/composer /usr/bin/composer
# PHPのライブラリリポジトリ(https://packagist.org)の日本用ミラーサイト(https://packagist.jp。さくらインターネット上にあるとのこと)をcomposerで使用するように設定。
# 本家がフランスにあるためそのままcomposerを走らせると通信に時間がかかるため設定。
RUN composer config -g repos.packagist composer https://packagist.jp
ENV COMPOSER_ALLOW_SUPERUSER 1
ENV COMPOSER_HOME /composer
ENV PATH $PATH:/composer/vendor/bin
COPY --chown=www-data:www-data ./ /var/www/html
RUN chmod 777 -R /var/www/html \
&& chmod 777 -R /var/www/html/storage \
&& chown -R www-data:www-data /var/www/html
COPY docker/php.ini /usr/local/etc/php/php.ini
COPY docker/php-fpm.conf /usr/local/etc/php-fpm.conf
COPY docker/php-fpm.d/www.conf /usr/local/etc/php-fpm.d/www.conf
RUN chmod 644 /usr/local/etc/php/php.ini \
&& chmod 644 /usr/local/etc/php-fpm.conf \
&& chmod 644 /usr/local/etc/php-fpm.d/www.conf \
&& mkdir -p /var/log/laravel \
&& chmod 777 -R /var/log/laravel \
&& chown -R www-data:www-data /var/log/laravel
ENV HOSTNAME 0.0.0.0
COPY docker/nginx/php.conf /etc/nginx/conf.d/php.conf
COPY docker/nginx/nginx.conf /etc/nginx/nginx.conf
RUN mkdir -p /app/docker
RUN mkdir -p /var/log/php-fpm
COPY docker/startup.sh /app/docker/startup.sh
RUN chmod 777 -R /app/docker \
&& chmod 777 -R /var/log/php-fpm \
&& chown www-data:www-data /app/docker \
&& chown www-data:www-data /app/docker
RUN chown -R www-data:www-data /var/lib/nginx
FROM base as local
EXPOSE 8080 9003
ENV PHP_IDE_CONFIG "serverName=host.docker.internal"
ENV XDEBUG_CONFIG "idekey=PHPSTORM"
RUN pecl install xdebug-3.1.6 \
&& docker-php-ext-enable xdebug
COPY docker/docker-php-ext-xdebug.ini /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
RUN chmod 644 /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
RUN touch /tmp/xdebug.log
RUN chmod 0777 /tmp/xdebug.log
# requwie-devのcomposerをインストール
WORKDIR /var/www/html
RUN composer install --dev
RUN composer dumpautoload --dev
RUN php artisan config:clear
RUN php artisan route:clear
#RUN php artisan test
CMD ["sh", "/app/docker/startup.sh"]
FROM base as development
EXPOSE 8080
## ユニットテスト実施に必要なenvファイルを配置。envファイルの中身はCloudBuildのトリガー内で定義
RUN echo "${_UNIT_TEST_ENV}" >> /var/www/html/.env.testing
# requwie-devのcomposerをインストール
WORKDIR /var/www/html
RUN composer install --dev
RUN composer dumpautoload --dev
RUN php artisan config:clear
RUN php artisan route:clear
#RUN php artisan test
CMD ["sh", "/app/docker/startup.sh"]
FROM base as production
EXPOSE 8080
WORKDIR /var/www/html
RUN composer install --optimize-autoloader --no-dev
RUN composer dumpautoload --no-dev
RUN php artisan config:clear
RUN php artisan route:clear
RUN set -ex; \
{ \
echo "; Cloud Run enforces memory & timeouts"; \
echo "memory_limit = -1"; \
echo "max_execution_time = 0"; \
echo "; File upload at Cloud Run network limit"; \
echo "upload_max_filesize = 500M"; \
echo "post_max_size = 500M"; \
echo "; Configure Opcache for Containers"; \
echo "opcache.enable = On"; \
echo "opcache.validate_timestamps = Off"; \
echo "; Configure Opcache Memory (Application-specific)"; \
echo "opcache.memory_consumption = 32"; \
} > "$PHP_INI_DIR/conf.d/cloud-run.ini"
CMD ["sh", "/app/docker/startup.sh"]
点数
-
- 「development」でcloudbuildtest.yamlにて設定したユニットテスト用の引数「_UNIT_TEST_ENV」を「/var/www/html/.env.testing」に出力しています。こうすることでartisanでユニットテストを走らせるときに必要な環境変数を用意しています。
-
- (.gitignoreで.env.testingを除外していないならそもそも不要な対応です。)
- 「production」で「opcache」を使用することでレスポンス速度を向上。ローカル環境で有効にするとソースの反映がされないので本番のみ有効化しています。
最后
最近在GCP上尝试了一下创建微服务,使用多阶段编排非常方便,比以前简化了很多!(以前为测试和生产环境分别建立了Dockerfile…)
虽然这次省略了,但GCP的各项服务的用户界面也非常简洁和易用,希望您能有机会试一试!
招聘工程师
由于我们公司正在急需工程师,如有兴趣者,请务必与我们联系!