前提条件
以前なら GCE マシーンイメージのビルドは Packer を利用して処理が実行できていました。しかし最近のアップデートにより(Packer なのか GCE なのかどちらか分かりませんが)仕様が変わってしまったようで現時点では マシーンイメージのビルドに Packer を採用できません。
今回は代替策として CircleCI と Ansible を利用して GCE マシーンイメージをビルドする方法を紹介します。
CircleCI Executor のビルド
まず初めに CircleCI での処理を実行するための Executor をビルドします。Ansible のバージョンは常に最新のものになります。状況に応じて Ansible のバージョンを固定します。
FROM python:3-slim
# Install required packages
RUN apt-get update \
&& mkdir -p /usr/share/man/man1 \
&& apt-get install -y \
apt ca-certificates curl git locales openssh-client sudo unzip
# Add User
RUN groupadd --gid 3434 circleci \
&& useradd --uid 3434 --gid circleci --shell /bin/bash --create-home circleci \
&& echo 'circleci ALL=NOPASSWD: ALL' >> /etc/sudoers.d/50-circleci \
&& echo 'Defaults env_keep += "DEBIAN_FRONTEND"' >> /etc/sudoers.d/env_keep
# Install Ansible
RUN sudo -u circleci pip3 install --user ansible
USER circleci
ENV PATH /home/circleci/.local/bin:/home/circleci/bin:${PATH}
CMD ["/bin/sh"]
サンプルコード になります。
サービスアカウントの発行
GCP のサービスアカウントを発行して CircleCI へ登録します。必要な権限はPackerのマニュアルを参考にします。
秘匿情報の登録
CircleCI の Context に以下の情報を登録します。
これは Orb circleci/gcp-cli で gcloud コマンドの設定をする時に必要な情報になります。
CLOUD_SERVICE_KEY
以下のようなものです。
{
"type": "service_account",
"project_id": "sample-project-263612",
"private_key_id": "552dcba6163bdda85b4f4fac12b09d80d77a3e843a",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCgDYZONQcSd5RV\nJb8TwpZNqjJkNvZWhgixWZkbsFQT5j9bHrCXy32M2gyWiuZdlg/PdufL6PDcotuC\nruJn0Nhp3aWk07n6NiFjNQX+ivxNz/JIhoClucFve8mgSKHVFZYF5SVymuEc7dG/\n3OOHTEogLt3jP0lN+/V+PlhgXcLSYURfphlTmqErIdvovo+rR+gCs0n7WusuHqXv\nUu7zx5WOcZu+MYgtYF7uENrt1Gr81SMKY/lp1WSzt4TTqLZxDEkk/JQ0VLc2ijDh\nxQdUv8fH6EwGaYX65B5BFr+jwawh2T2N69yGEqo3w0rYmCbxq25p/UWaXjFfLHgX\nY5PiVdXhAgMBAAECggEAA34wEWpeWGSUL+SGPkHnolPFzEKXzy0XKPmt2fkX6KRE\nezJZA8MM3yNOFYX7/4xcFXjAl8ZiZUpHlddwdI9R+NbYgJCqcV33cfqNx5L3nF9jpws\nazFGQveiFVMEBDO5lmslbkUBM3ZWVdmJ05BYRdZgIsIrFP/HU4SdZ68oIhhBvXIZ\nmYcbxyhSgNMUsXzwqj6/UVCEER9fUVGDG95JU9WpLAP26gRcqhJTqVRmX+SaQP8z\nJBhh8UHBeWJ3cCFuV3zFv+SmdxnY/pXknOmabyl8X+gOJLB3TSctWSoJSJBQ6O5J\n3R69jLaWqmmSmFGGZtriR7DS+R8GjbwDyFk3rv/uQQKBgQDfNp5Bz2dwkxegrIAm\niVzDWOrd6/lO40vK8UemN2u7hbwxnmti4W9S+N6WMubZShGa97Nb1OURHPujJEhg\nzqmE5b2oHBwccvFSFS1a7z4soxFKTQwnKOxfH1PMJlETX1K6ypwpYPUItBA/1kS8\nbLLm7cU/oQ7HFzT58iSafWqXMQKBgQC3j+mVJzJmfP9axRouv6s7kBU6256RNJ1Z\nvQt5CpRSX36FpadTtixt+Kz5Xiuyb4RHXDSokJAUBbfXYDJiRew3df9WRQdMwlyNq\n5LemiCezr00KoTnunfmgMe+eUViG/uYgyE3sm+U54RbfiLnCPu9JfydNn9DLHNzd\nb+3uTX7dsQKBgGaRr4koKC0nuky/16ddqX2uh0Zw37/rXHGmC7mKb/vciUz3sfrf\nAovLBmVYgJHKompmdkm1e4kwm0UtMAEkFeuWsl7kg9piyxdf2daWZyiVyiUtYG9C\nM9PGDniaBtlzDQ3+emHdRtu3+luLN0yqWk8ZZXFGrga4WESei0leZjORAoGAVSQ9\nVd9uczc7QiD2OgTRKbl0/23qqLNc4Mjcz3HmxiZhCyCA8kUnaoLTyH6zifpLwsWz\n7xPOoFreoNmPxSDARtjmeI+to3YXCXe471dsAt4mv+10b4d6x6Eh4a8dftAwcbg2\n3K6arjQHZfuHHeAPIWoHEuwz7mIe198Y31W2qKECgYEAmQLmyoYFUnF26PusW0+d\nl1mLGblznr9h9mCfh52hgV+D6xTI7vn2gUaonWHM6nnJPc28LZnDNLJRzotHkIBl\nAhXAgDvF357dr8Jp5rSV6Ssf77P93s4XKzjKJmEKnQbDIm53RgFdtisGufkWQq73\njb8Zh+KhFEFiLEnU4igc4K0=\n-----END PRIVATE KEY-----\n",
"client_email": "mamono210@sample-project-263612.iam.gserviceaccount.com",
"client_id": "109182338149199700825",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/mamono210%40sample-project-263612.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}
GOOGLE_COMPUTE_REGION
Google Cloud Platform (GCP)の利用可能なリージョンとゾーンは頻繁に更新される可能性があります。以下のような値を選びます。
us-central1
us-west1
us-east1
us-east4
us-west2
us-west3
us-west4
europe-west1
europe-west2
europe-west3
europe-west4
europe-west6
europe-central2
asia-east1
asia-east2
asia-south1
asia-southeast1
asia-southeast2
asia-northeast1
asia-northeast2
asia-northeast3
southamerica-east1
northamerica-northeast1
northamerica-northeast2
australia-southeast1
australia-southeast2
GOOGLE_COMPUTE_ZONE
以下のような値になります。リージョンによってことなります。
a
b
c
d
GOOGLE_PROJECT_ID
Google Cloud Platform のプロジェクトIDです。
CircleCI の設定ファイルの実装
CircleCI の設定ファイルで以下の処理を記述していきます。サンプルコードはこちらになります。
- gcloud コマンドのインストール
- GCE インスタンスの起動
- SSHキーの発行・登録
- Ansible playbook の実行
- インスタンスをマシーンイメージへ登録
- インスタンスの破棄
version: 2.1
executors:
packer:
docker:
- image: ghcr.io/docker-images-mamono210/circleci-executors/ansible:latest
resource_class: small
orbs:
gcp-cli: circleci/gcp-cli@3.1.0
jobs:
packer:
executor: packer
parameters:
boot-disk-size:
type: string
image-family:
type: string
image-project:
type: string
instance-name:
type: string
machine-type:
default: n1-standard-1
type: string
machine-image-name:
type: string
project-name:
type: string
service-account:
type: string
zone:
type: string
steps:
- checkout
- gcp-cli/setup
- run:
name: Set environment variables to create unique resources
command: |
# 一意のリソースを作成するためにタイムスタンプを利用して変数を設定する
TIMESTAMP=$(date --date "9 hours" "+%Y%m%d-%H%M%S")
echo "export INSTANCE_NAME=<< parameters.instance-name>>-${TIMESTAMP}" >> $BASH_ENV
echo "export MACHINE_IMAGE_NAME=<< parameters.machine-image-name >>-${TIMESTAMP}" >> $BASH_ENV
source $BASH_ENV
- run:
name: Create instance
command: |
# Ansible を実行する対象のインスタンスを Ansible 実行前に起動しておく
gcloud compute instances create ${INSTANCE_NAME} \
--project=<< parameters.project-name >> \
--zone=<< parameters.zone >> \
--image-project=<< parameters.image-project >> \
--image-family=<< parameters.image-family >> \
--machine-type=<< parameters.machine-type >> \
--boot-disk-size=<< parameters.boot-disk-size >> \
--network-interface=network-tier=PREMIUM,stack-type=IPV4_ONLY,subnet=default \
--maintenance-policy=MIGRATE \
--provisioning-model=STANDARD \
--service-account=<< parameters.service-account >> \
--scopes=https://www.googleapis.com/auth/compute,https://www.googleapis.com/auth/logging.write,https://www.googleapis.com/auth/monitoring.write,https://www.googleapis.com/auth/trace.append,https://www.googleapis.com/auth/servicecontrol,https://www.googleapis.com/auth/service.management.readonly,https://www.googleapis.com/auth/devstorage.read_only \
--no-shielded-secure-boot \
--shielded-vtpm \
--shielded-integrity-monitoring \
--reservation-affinity=any
- run:
name: Create SSH Key
command: |
# Ansible がインスタンスへ接続するためのSSH鍵を発行する
gcloud compute ssh centos@${INSTANCE_NAME} \
--command "cat /etc/redhat-release" \
--zone=<< parameters.zone >> \
--quiet
# Set Strict host key checking no
echo "StrictHostKeyChecking no" > ~/.ssh/config
- run:
name: Update OS
command: |
# ansible-playbook コマンドを実行して対象のシステムをセットアップする
# 環境変数に ANSIBLE_FORCE_COLOR、PY_COLORS を設定すると Ansible の実行ログがカラー表示になる
# Get external Ip addr
ip_addr=$(
gcloud compute instances describe ${INSTANCE_NAME} \
--format='get(networkInterfaces[0].accessConfigs[0].natIP)' \
--zone=<< parameters.zone >> \
)
# Execute Ansible playbook
ansible-playbook -i ${ip_addr}, \
-u centos \
--key-file="~/.ssh/google_compute_engine" \
update.yml
environment:
ANSIBLE_FORCE_COLOR: '1'
PY_COLORS: '1'
TZ: 'Asia/Tokyo'
- run:
name: Register machine image
command: |
gcloud beta compute machine-images create ${MACHINE_IMAGE_NAME} \
--project=<< parameters.project-name >> \
--source-instance=${INSTANCE_NAME} \
--source-instance-zone=<< parameters.zone >> \
--storage-location=us
- run:
name: Delete instance
command: |
gcloud compute instances delete ${INSTANCE_NAME} \
--project=<< parameters.project-name >> \
--zone=<< parameters.zone >>
workflows:
version: 2.1
packer:
jobs:
- packer:
boot-disk-size: '40'
context: GCLOUD
machine-image-name: 'centos-stream-8-golden-image'
machine-type: 'n1-standard-1'
image-family: 'centos-stream-8'
image-project: 'centos-cloud'
instance-name: 'centos-stream-8-golden-image-builder'
project-name: 'buoyant-world-263612'
service-account: 'mamono210@buoyant-world-263612.iam.gserviceaccount.com'
zone: 'us-central1-a'
まとめ
Packer が利用できない現在の状況下での GCE マシーンイメージのビルド方法の代替案の1つとして参考になれば幸いです。
その他
サンプルコードのように OS のアップデートのみなら Ansible ではなくコマンドで実行した方が簡単です。
ssh user@remote-server-ip "sudo apt-get update; sudo apt upgrade"
本来ならこのサンプルコードでは Ansible を使うべき場面ではないと思いますが説明を分かりやすくするために Ansible playbook を簡単な処理にしてあります。