はじめに
記事「OCI初心者向け:Fn Project CLIでOCI Functionsにデプロイしてみた」では、ローカル PC の Fn Project CLI を使って OCI Functions に Python 関数をデプロイしました。
その記事の冒頭で、OCI Functions のデプロイ方法は大きく 3 つあると紹介しました。
- Fn Project CLI を使う(前回記事で紹介)
- AWS Lambda デプロイ時に使う AWS SAM CLI と近い機能を備えているのが Fn Project CLI です。ローカルでの動作確認や、ローカルからのデプロイを行えます。
- OCI DevOps を使う
- AWS Lambda を AWS CodePipeline、AWS CodeBuild でデプロイする流れを OCI Functions で行いたい場合は、OCI DevOps で行えます。ソースコード・リポジトリ(GitHub や OCI Code Repositories など)と連携し、CI/CD パイプラインを実現したい場合に使用できます。
- Terraform を使う
- OCI Functions はコンテナベースで動作します。事前にコードをDockerビルドし、OCI Container Registry (OCIR) にプッシュした後に、Terraform で、OCI Functions の定義を作成できます。
本記事では、このうち 「OCI DevOps を使う」方法を扱います。OCI 側のリソースを可能な限り Terraform で構築し、GitHub との接続などの人手作業を最小限にすることをゴールにします。前回はコンソール上手作業でネットワーク(VCN)まわりを構築しましたが、今回は Terraform で作り直します。
今回のゴール
GitHub リポジトリに置いた Python 関数コードを git push すると、
OCI DevOps の ビルド・パイプラインが自動でコンテナ・イメージをビルドし、
OCIR(OCI Container Registry)へ push、
続けて デプロイ・パイプラインが OCI Functions を新しいイメージへ更新する。
この一連の CI/CD を、OCI 側は Terraform で構築します。
AWS で例えると
| 役割 | AWS | OCI(本記事) |
|---|---|---|
| ソース | GitHub / CodeCommit | GitHub |
| ビルド | CodeBuild | OCI DevOps(ビルド) |
| コンテナ・レジストリ | ECR | OCIR |
| パイプライン制御 | CodePipeline | OCI DevOps(ビルド+デプロイ・パイプライン) |
| 実行環境 | Lambda | OCI Functions |
| IaC | CloudFormation / Terraform | Terraform |
アーキテクチャ全体像
Terraform で作成する OCI リソースは次のとおりです。プレフィクスは handson-functions-devops- としています。
| 分類 | リソース | 名前(プレフィクス handson-functions-devops-) |
|---|---|---|
| ネットワーク | VCN | handson-functions-devops-vcn |
| サブネット(パブリック) | handson-functions-devops-subnet1 |
|
| インターネット・ゲートウェイ | handson-functions-devops-igw |
|
| ルート表 | handson-functions-devops-rt |
|
| レジストリ | OCIR リポジトリ | handson-functions-devops-repository/hello |
| Functions | アプリケーション | handson-functions-devops-app |
| 関数 | handson-functions-devops-hello |
|
| Vault | Vault / キー / シークレット | handson-functions-devops-vault ほか |
| IAM | 動的グループ / ポリシー | handson-functions-devops-dg / -policy |
| DevOps | プロジェクト | handson-functions-devops-project |
| GitHub 接続 | handson-functions-devops-github-connection |
|
| ビルド・パイプライン | handson-functions-devops-build-pipeline |
|
| デプロイ・パイプライン | handson-functions-devops-deploy-pipeline |
|
| トリガー | handson-functions-devops-github-trigger |
|
| 通知 | Notification トピック | handson-functions-devops-topic |
| ロギング | ロググループ / ログ | handson-functions-devops-log-group / -devops-log |
やること / 前提
前提
前回記事の前提に加えて、いくつか増えます。増えた項目が太字です。
- OCI アカウント作成済み(前回同様、個人アカウントを想定し Administrator 権限で実施。本番ではドメイン/グループ/ポリシーで権限を絞ることを推奨)
- ローカル PC に Docker インストール済み
- OCI CLI をセットアップ済み
- Terraform インストール済み
- GitHub アカウントとリポジトリ
- GitHub の Personal Access Token(PAT)(OCI DevOps が GitHub に接続するために使用)
💡 前回記事では「OCI CLI をインストールして、ローカル PC に OCI の config(AWS で言うプロファイル)を作成」しました。本記事の Terraform も、その config(API キー)を使って OCI へ接続します。
作業の流れ
- GitHub リポジトリの準備(Python 関数コード・
Dockerfile・build_spec.yamlを push) - GitHub PAT の発行
- Terraform 変数の設定(
terraform.tfvars) terraform applyで OCI リソースを一括作成- GitHub Webhook の登録(GitHub 側手作業。詳細は後述)
git pushして自動ビルド〜デプロイを確認
OCI 側の作成は基本的に手順 4 の terraform apply に集約されます。前回コンソールで作成した VCN・サブネット・IGW・OCIR・Functions アプリなども、すべて Terraform 定義に含めます。
1. GitHub リポジトリの準備
リポジトリのルートには次のファイルを置きます。
. ├── func.py # Python 関数本体(前回の hello-python 相当) ├── func.yaml # Fn の関数定義(ローカル実行・確認用) ├── requirements.txt # Python 依存 ├── Dockerfile # OCI DevOps のマネージド・ビルドが docker build で使用 ├── build_spec.yaml # OCI DevOps ビルド・パイプラインのビルド仕様 └── terraform/ # OCI リソースの Terraform 定義
func.py は前回の hello-python と同じく、{"message": "Hello World"} を返すサンプルです。
import io
import json
import logging
from fdk import response
def handler(ctx, data: io.BytesIO = None):
name = "World"
try:
body = json.loads(data.getvalue())
name = body.get("name", "World")
except (Exception, ValueError) as ex:
logging.getLogger().info("error parsing json payload: " + str(ex))
logging.getLogger().info("Inside Python Hello World function")
return response.Response(
ctx,
response_data=json.dumps({"message": "Hello {0}".format(name)}),
headers={"Content-Type": "application/json"},
)
Dockerfile を用意する理由
前回は fn deploy が内部で Docker イメージをビルドしてくれていました。OCI DevOps のマネージド・ビルドでは、ビルド・ランナー上でコンテナ・イメージのビルドを実行するため、Dockerfile を自前で用意します。中身は fn init --runtime python が生成するものと同等のマルチステージ構成です。
FROM fnproject/python:3.11-dev AS build-stage
WORKDIR /function
ADD requirements.txt /function/
RUN pip3 install --target /python/ --no-cache --no-cache-dir -r requirements.txt \
&& rm -fr ~/.cache/pip /tmp* requirements.txt func.yaml Dockerfile .venv
ADD . /function/
RUN rm -fr /function/.pip_cache
FROM fnproject/python:3.11
WORKDIR /function
COPY --from=build-stage /python /python
COPY --from=build-stage /function /function
RUN chmod -R o+r /function
ENV PYTHONPATH=/function:/python
ENTRYPOINT ["/python/bin/fdk", "/function/func.py", "handler"]
build_spec.yaml
OCI DevOps のビルド・パイプライン(マネージド・ビルド・ステージ)が読み込むビルド仕様です。リポジトリのルートに置くと、ビルド・ステージのデフォルトパス(build_spec.yaml)として自動的に参照されます。
version: 0.1
component: build
timeoutInSeconds: 6000
shell: bash
env:
exportedVariables:
# ビルド実行ごとに一意なイメージタグ。デプロイ・パイプラインへ引き継ぐ。
- BUILDRUN_HASH
steps:
- type: Command
name: "イメージタグ (BUILDRUN_HASH) を採番する"
command: |
export BUILDRUN_HASH=$(echo ${OCI_BUILD_RUN_ID} | rev | cut -c 1-7)
echo "BUILDRUN_HASH: ${BUILDRUN_HASH}"
- type: Command
name: "関数コンテナ・イメージをビルドする"
command: |
cd ${OCI_PRIMARY_SOURCE_DIR}
# OL8 ビルド・ランナーのコンテナ・エンジンは podman(OL7 は docker)。
if command -v docker >/dev/null 2>&1; then CE=docker; else CE=podman; fi
echo "container engine: $CE"
$CE build -t fn-image:${BUILDRUN_HASH} -f Dockerfile .
outputArtifacts:
- name: fn_image
type: DOCKER_IMAGE
location: fn-image:${BUILDRUN_HASH}
ポイントは 2 つです。
BUILDRUN_HASH:OCI DevOps の定義済み変数OCI_BUILD_RUN_IDから一意なタグを生成し、exportedVariablesでデプロイ・パイプライン側へ引き継ぎます。「毎回異なるタグを付けて push し、そのタグで関数を更新する」ための仕掛けです。outputArtifacts[].name(fn_image):後続の Deliver Artifact ステージが、この名前でビルド成果物(Docker イメージ)を OCIR へ紐付けます。
⚠️ コンテナ・エンジンは
dockerとは限りません。 OCI DevOps のビルド・ランナーは OL7 がサポート終了し、現在は Oracle Linux 8(OL8) を指定します。OL8 ランナーではコンテナ・エンジンが Podman に置き換わっているため、docker buildを直書きすると失敗する可能性があります。上記のようにエンジンを自動判定しておくと、どちらのランナーでも動きます。
2. GitHub PAT の発行
OCI DevOps が GitHub リポジトリを読み取るために、GitHub の Personal Access Token(PAT) を使います。
- GitHub → Settings → Developer settings → Personal access tokens で発行します。
- スコープは、プライベートリポジトリなら
repoと、admin:repo_hookを付与します。 - 発行した PAT は、後述の Terraform 変数として渡し、OCI Vault のシークレットに安全に格納します。
3.〜4. Terraform で OCI リソースを一括作成
ここが本記事の主役です。terraform/ ディレクトリに OCI リソースをすべて定義し、terraform apply で作成します。
ファイル構成
リソースの種類ごとにファイルを分けています。
terraform/ ├── versions.tf / provider.tf # プロバイダのバージョンと認証設定 ├── variables.tf / locals.tf # 入力変数と、そこから導出する値 ├── network.tf # VCN / サブネット / IGW / ルート表 ├── registry.tf # OCIR リポジトリ ├── vault.tf # Vault / キー / GitHub PAT シークレット ├── iam.tf # 動的グループ / ポリシー ├── functions.tf # Functions アプリ / 関数 / seed イメージ push ├── devops.tf # DevOps プロジェクト / 接続 / ビルド / デプロイ / トリガー ├── logging.tf # DevOps ログ(これが無いとビルドが走りません) ├── outputs.tf # 関数 OCID や Webhook URL などの出力 └── terraform.tfvars.example # ← これをコピーして terraform.tfvars を作る
変数の設定(terraform.tfvars)
環境ごとに変わる値は、すべて変数として外に出しています。terraform.tfvars.example をコピーして terraform.tfvars を作り、自分の値を書き込みます。
cd terraform cp terraform.tfvars.example terraform.tfvars
terraform.tfvars には GitHub PAT と OCI 認証トークンという 2 つの秘密情報が入ります。
# ---- OCI 認証・共通 ---- tenancy_ocid = "ocid1.tenancy.oc1..xxxxxxxx" compartment_ocid = "ocid1.compartment.oc1..xxxxxxxx" region = "ap-tokyo-1" oci_config_profile = "DEFAULT" # prefix = "handson-functions-devops-" # 変更する場合のみ # ---- ロギング ---- # log_retention_duration = 30 # DevOps ログの保持日数(変更する場合のみ) # ---- OCIR(seed イメージ push 用)---- ocir_region_key = "nrt" # 東京=nrt / 大阪=kix ocir_username = "taro.oracle@example.com" # フェデレーションユーザーは oracleidentitycloudservice/<user> ocir_auth_token = "xxxxxxxxxxxxxxxxxxxx" # OCI 認証トークン(GitHub PAT とは別物) auto_push_seed_image = true # ---- GitHub ---- github_pat = "ghp_xxxxxxxxxxxxxxxxxxxx" github_repository_url = "https://github.com/<owner>/oci-functions-devops-handson.git" github_branch = "develop"
各変数の意味と取得方法は次のとおりです。
| 変数 | 必須 | 説明・取得方法 |
|---|---|---|
tenancy_ocid |
✅ | テナンシの OCID。コンソール右上のプロフィール → テナンシ、または oci iam compartment list の親。動的グループはテナンシ直下に作るため必要です。 |
compartment_ocid |
✅ | リソースを作るコンパートメントの OCID。個人アカウントでルート直下に作るならテナンシ OCID と同じ値で構いません。 |
region |
リージョン識別子。デフォルトは ap-tokyo-1。 |
|
oci_config_profile |
~/.oci/config のプロファイル名。前回記事で作った config をそのまま使います。デフォルトは DEFAULT。 |
|
prefix |
作成するリソース名のプレフィクス。デフォルトは handson-functions-devops-。前回の handson-functions- と衝突させないための値です。 |
|
log_retention_duration |
DevOps ログの保持日数。デフォルト 30 日。 | |
ocir_region_key |
OCIR のリージョンキー。東京は nrt、大阪は kix。 |
|
ocir_username |
seed push 時 | OCIR への docker login に使うユーザー名の「ネームスペース/」以降。通常ユーザーはメールアドレス、IDCS フェデレーションユーザーは oracleidentitycloudservice/。 |
ocir_auth_token |
seed push 時 | OCI の認証トークン。コンソールのユーザー詳細 → 認証トークンで発行します。GitHub PAT とは別物なので注意。 |
auto_push_seed_image |
true なら terraform apply の中で seed イメージを build & push します。ローカルに docker が無い場合は false にして手動 push に切り替えます。 |
|
github_pat |
✅ | 2. で発行した GitHub PAT。Vault シークレットに格納されます。プライベートリポジトリなら repo スコープが必要です。 |
github_repository_url |
✅ | 接続する GitHub リポジトリの URL(.git 付き)。 |
github_branch |
ビルドのトリガーにするブランチ。デフォルトは develop。ビルド・ソースとトリガー・フィルタの両方で使われるので、実際に push するブランチと一致させてください。 |
ネットワーク(VCN)を Terraform で作成する
前回記事では、コンソールで次のように手作業で作成していました。
- VCN を作成します。最低限、名前:handson-functions-vcn、IPv4 CIDR ブロック:10.1.0.0/16、DNS 解決:OFF を設定
- サブネットを作成します。名前:handson-functions-subnet1、IPv4 CIDR ブロック:10.1.0.0/24、パブリックサブネットを選択
- インターネットゲートウェイの作成と、ルート・ルールを設定します。名前:handson-functions-igw
- ターゲットタイプ:インターネット・ゲートウェイ、宛先 CIDR ブロック:0.0.0.0/0
今回はこれを VCN から丸ごと Terraform 化します。CIDR などの値は前回に合わせつつ、名前のプレフィクスだけ handson-functions-devops- にします。
resource "oci_core_vcn" "vcn" {
compartment_id = var.compartment_ocid
cidr_blocks = ["10.1.0.0/16"]
display_name = "${var.prefix}vcn"
}
resource "oci_core_internet_gateway" "igw" {
compartment_id = var.compartment_ocid
vcn_id = oci_core_vcn.vcn.id
display_name = "${var.prefix}igw"
enabled = true
}
resource "oci_core_route_table" "rt" {
compartment_id = var.compartment_ocid
vcn_id = oci_core_vcn.vcn.id
display_name = "${var.prefix}rt"
route_rules {
destination = "0.0.0.0/0"
network_entity_id = oci_core_internet_gateway.igw.id
}
}
resource "oci_core_subnet" "subnet1" {
compartment_id = var.compartment_ocid
vcn_id = oci_core_vcn.vcn.id
cidr_block = "10.1.0.0/24"
display_name = "${var.prefix}subnet1"
route_table_id = oci_core_route_table.rt.id
security_list_ids = [oci_core_vcn.vcn.default_security_list_id]
}
⚠️ 前回記事の最後で触れられていた、こんなハマりどころがありました。
※ インターネットゲートウェイの作成とルート・ルールの設定を忘れているとここで、下記エラーとなります。
Error invoking function. status: 502 message: Failed to pull function imageOCI Functions はイメージを OCIR から pull するため、egress 経路(IGW とルート)が必須です。Terraform 化するとこの設定漏れを防げるのが利点です。
OCIR・Functions アプリ・関数
OCIR リポジトリ、Functions アプリ、関数を作成します。関数はデプロイ・パイプラインが更新しますが、関数リソース自体は事前に存在させる必要があるため、初回だけ「seed(種)イメージ」を指定して作成します。
resource "oci_artifacts_container_repository" "fn_repo" {
compartment_id = var.compartment_ocid
display_name = "${var.prefix}repository/hello"
is_public = false
}
resource "oci_functions_application" "app" {
compartment_id = var.compartment_ocid
display_name = "${var.prefix}app"
subnet_ids = [oci_core_subnet.subnet1.id]
}
resource "oci_functions_function" "hello" {
application_id = oci_functions_application.app.id
display_name = "${var.prefix}hello"
memory_in_mbs = 256
image = "${local.ocir_image_base}:seed"
# CI/CD(デプロイ・パイプライン)が image を更新するため、
# Terraform 側では image の変更を無視してドリフトを防ぐ。
lifecycle {
ignore_changes = [image, image_digest]
}
depends_on = [null_resource.seed_image]
}
📝 seed イメージについて:OCI Functions は「イメージ URI を持つ関数」として作成されるため、初回作成時点で OCIR に最低 1 つイメージが必要です。しかし作りたての OCIR は空です。そこで
terraform applyの中で、null_resource+local-execを使って seed イメージを 1 回だけ build & push します(詳細は README を参照)。以降の更新はすべて DevOps のデプロイ・パイプラインが${BUILDRUN_HASH}タグで行います。
Vault に GitHub PAT を格納
2.で発行した PAT を、OCI Vault のシークレットとして格納します。OCI DevOps の GitHub 接続は、このシークレットの OCIDを参照します(PAT を平文で持ちません)。
resource "oci_kms_vault" "vault" {
compartment_id = var.compartment_ocid
display_name = "${var.prefix}vault"
vault_type = "DEFAULT"
}
resource "oci_kms_key" "key" {
compartment_id = var.compartment_ocid
display_name = "${var.prefix}key"
management_endpoint = oci_kms_vault.vault.management_endpoint
key_shape {
algorithm = "AES"
length = 32 # バイト単位。AES-256。
}
}
resource "oci_vault_secret" "github_pat" {
compartment_id = var.compartment_ocid
vault_id = oci_kms_vault.vault.id
key_id = oci_kms_key.key.id
secret_name = "${var.prefix}github-pat"
secret_content {
content_type = "BASE64"
content = base64encode(var.github_pat)
}
}
IAM(動的グループ / ポリシー)
DevOps のビルド/デプロイ・パイプラインが「OCIR への push」「Functions の更新」「PAT シークレットの読み取り」などを行えるよう、動的グループとポリシーを作成します。
resource "oci_identity_dynamic_group" "devops_dg" {
compartment_id = var.tenancy_ocid
name = "${var.prefix}dg"
description = "OCI DevOps build/deploy pipelines for handson"
matching_rule = "ANY {resource.type='devopsbuildpipeline', resource.type='devopsdeploypipeline', resource.type='devopsconnection', resource.type='devopstrigger'}"
}
resource "oci_identity_policy" "devops_policy" {
compartment_id = var.compartment_ocid
name = "${var.prefix}policy"
description = "Allow OCI DevOps to build, push to OCIR and deploy Functions"
statements = [
"Allow dynamic-group ${oci_identity_dynamic_group.devops_dg.name} to manage repos in compartment id ${var.compartment_ocid}",
"Allow dynamic-group ${oci_identity_dynamic_group.devops_dg.name} to manage functions-family in compartment id ${var.compartment_ocid}",
"Allow dynamic-group ${oci_identity_dynamic_group.devops_dg.name} to use virtual-network-family in compartment id ${var.compartment_ocid}",
"Allow dynamic-group ${oci_identity_dynamic_group.devops_dg.name} to read secret-family in compartment id ${var.compartment_ocid}",
"Allow dynamic-group ${oci_identity_dynamic_group.devops_dg.name} to use keys in compartment id ${var.compartment_ocid}",
"Allow dynamic-group ${oci_identity_dynamic_group.devops_dg.name} to manage devops-family in compartment id ${var.compartment_ocid}",
]
}
OCI DevOps(プロジェクト / 接続 / ビルド / デプロイ / トリガー)
いよいよ CI/CD 本体です。ここは構成要素が多いので、順番に作ります。
プロジェクトと通知トピック(DevOps プロジェクトは通知トピックが必須)
resource "oci_ons_notification_topic" "topic" {
compartment_id = var.compartment_ocid
name = "${var.prefix}topic"
}
resource "oci_devops_project" "project" {
compartment_id = var.compartment_ocid
name = "${var.prefix}project"
notification_config {
topic_id = oci_ons_notification_topic.topic.topic_id
}
}
ログの有効化(これが無いとビルドが走りません)
見落としやすいのですが、DevOps プロジェクトは OCI Logging のサービス・ログを有効化していないとビルド実行そのものが失敗します。コンソールでは「可観測性と管理 → ロギング → サービス・ログの有効化」に相当する設定で、Terraform では次のように書きます。
resource "oci_logging_log_group" "devops" {
compartment_id = var.compartment_ocid
display_name = "${var.prefix}log-group"
}
resource "oci_logging_log" "devops" {
log_group_id = oci_logging_log_group.devops.id
display_name = "${var.prefix}devops-log"
log_type = "SERVICE"
is_enabled = true
configuration {
compartment_id = var.compartment_ocid
source {
service = "devops"
source_type = "OCISERVICE"
category = "all" # コンソール表示は「DevOps Logs」
resource = oci_devops_project.project.id
}
}
retention_duration = 30
}
GitHub 接続(PAT シークレットの OCID を参照)
resource "oci_devops_connection" "github" {
project_id = oci_devops_project.project.id
connection_type = "GITHUB_ACCESS_TOKEN"
display_name = "${var.prefix}github-connection"
access_token = oci_vault_secret.github_pat.id # シークレットの OCID を渡す
}
デプロイ成果物(Docker イメージ)
OCIR 上のイメージ URI を指すアーティファクトです。${BUILDRUN_HASH} をビルドから受け取って解決できるよう、SUBSTITUTE_PLACEHOLDERS を指定します。
resource "oci_devops_deploy_artifact" "fn_image" {
project_id = oci_devops_project.project.id
display_name = "${var.prefix}fn-image"
deploy_artifact_type = "DOCKER_IMAGE"
argument_substitution_mode = "SUBSTITUTE_PLACEHOLDERS"
deploy_artifact_source {
deploy_artifact_source_type = "OCIR"
image_uri = "${local.ocir_image_base}:$${BUILDRUN_HASH}"
}
}
デプロイ・パイプライン(Functions へデプロイ)
resource "oci_devops_deploy_pipeline" "deploy_pipeline" {
project_id = oci_devops_project.project.id
display_name = "${var.prefix}deploy-pipeline"
}
resource "oci_devops_deploy_environment" "fn_env" {
project_id = oci_devops_project.project.id
display_name = "${var.prefix}fn-env"
deploy_environment_type = "FUNCTION"
function_id = oci_functions_function.hello.id
}
resource "oci_devops_deploy_stage" "deploy_fn" {
deploy_pipeline_id = oci_devops_deploy_pipeline.deploy_pipeline.id
display_name = "${var.prefix}deploy-fn-stage"
deploy_stage_type = "DEPLOY_FUNCTION"
function_deploy_environment_id = oci_devops_deploy_environment.fn_env.id
docker_image_deploy_artifact_id = oci_devops_deploy_artifact.fn_image.id
deploy_stage_predecessor_collection {
# デプロイ・パイプラインの最初のステージなので、先行はパイプライン自身の OCID。
items {
id = oci_devops_deploy_pipeline.deploy_pipeline.id
}
}
}
ビルド・パイプライン(3 ステージ:Build → Deliver → Trigger Deployment)
resource "oci_devops_build_pipeline" "build_pipeline" {
project_id = oci_devops_project.project.id
display_name = "${var.prefix}build-pipeline"
}
# ① マネージド・ビルド(コンテナ・イメージのビルド)
resource "oci_devops_build_pipeline_stage" "build" {
build_pipeline_id = oci_devops_build_pipeline.build_pipeline.id
display_name = "${var.prefix}build-stage"
build_pipeline_stage_type = "BUILD"
build_spec_file = "build_spec.yaml"
image = "OL8_X86_64_STANDARD_10"
primary_build_source = "app-source"
build_source_collection {
items {
name = "app-source"
connection_type = "GITHUB"
connection_id = oci_devops_connection.github.id
repository_url = var.github_repository_url
branch = var.github_branch
}
}
build_pipeline_stage_predecessor_collection {
# 最初のステージなので、先行はビルド・パイプライン自身の OCID。
items {
id = oci_devops_build_pipeline.build_pipeline.id
}
}
}
# ② デリバリ(ビルド成果物を OCIR へ push)
resource "oci_devops_build_pipeline_stage" "deliver" {
build_pipeline_id = oci_devops_build_pipeline.build_pipeline.id
display_name = "${var.prefix}deliver-stage"
build_pipeline_stage_type = "DELIVER_ARTIFACT"
deliver_artifact_collection {
items {
artifact_name = "fn_image" # build_spec.yaml の outputArtifacts[].name と一致
artifact_id = oci_devops_deploy_artifact.fn_image.id
}
}
build_pipeline_stage_predecessor_collection {
items {
id = oci_devops_build_pipeline_stage.build.id
}
}
}
# ③ デプロイ・パイプライン起動
resource "oci_devops_build_pipeline_stage" "trigger_deploy" {
build_pipeline_id = oci_devops_build_pipeline.build_pipeline.id
display_name = "${var.prefix}trigger-deploy-stage"
build_pipeline_stage_type = "TRIGGER_DEPLOYMENT_PIPELINE"
deploy_pipeline_id = oci_devops_deploy_pipeline.deploy_pipeline.id
is_pass_all_parameters_enabled = true # BUILDRUN_HASH をデプロイへ引き継ぐ
build_pipeline_stage_predecessor_collection {
items {
id = oci_devops_build_pipeline_stage.deliver.id
}
}
}
トリガー(GitHub の push でビルドを起動)
resource "oci_devops_trigger" "github" {
project_id = oci_devops_project.project.id
display_name = "${var.prefix}github-trigger"
trigger_source = "GITHUB"
connection_id = oci_devops_connection.github.id
actions {
type = "TRIGGER_BUILD_PIPELINE"
build_pipeline_id = oci_devops_build_pipeline.build_pipeline.id
filter {
trigger_source = "GITHUB"
events = ["PUSH"]
include {
head_ref = var.github_branch
}
}
}
}
apply する
定義がそろったら、Terraform を実行します。
cd terraform terraform init terraform plan terraform apply
apply の中では、依存関係にしたがって次の順に作られます。
- ネットワーク(VCN / サブネット / IGW / ルート表)
- OCIR リポジトリ / Vault / キー / シークレット / IAM
- seed イメージの build & push(
null_resource.seed_image) - Functions アプリ / 関数(seed イメージで作成)
- DevOps プロジェクト / ログ / GitHub 接続 / ビルド・デプロイ・パイプライン / トリガー
Vault の作成に 3〜4 分、キーの作成にさらに 2 分ほどかかるので、全体では 10 分弱を見ておくとよいです。
完了したら、後続の作業で使う値を出力から拾えます。
terraform output # 一覧 terraform output -raw function_id # 関数の OCID(動作確認用) terraform output -raw trigger_webhook_url # GitHub Webhook の Payload URL
5. GitHub Webhook の登録(GitHub 側手作業)
OCI DevOps のトリガーは「GitHub からの Webhook を受け取る URL」と、署名検証用の Secret を提供します。push を起点に自動実行するには、この 2 つを GitHub リポジトリの Webhook に登録します。OCI DevOps は GitHub 側の Webhook を自動登録しないため、ここだけは GitHub 側の作業になります。
- Webhook URL は Terraform の出力(
terraform output -raw trigger_webhook_url)から取得できます。 - GitHub リポジトリ → Settings → Webhooks → Add webhook で登録します(Content type は
application/json、イベントは push)。
⚠️ Secret は必須で、しかも Terraform では取得できません。
OCI の GitHub トリガーの Secret は CreateTrigger API のレスポンス(GithubTriggerCreateResult.secret)にだけ返る値で、作成後は API でもコンソールでも再取得できません。Terraform プロバイダはこの値を state に保存しないため、oci_devops_triggerだけで作ったトリガーに Webhook を向けると、GitHub の Recent Deliveries で 404 になります。{ "code" : "NotAuthorizedOrNotFound", "message" : "Invalid payload URL or secret" }
そのため トリガーだけは OCI CLI で作成して Secret を控え、Terraform には
importして管理下に戻すのが確実です。
cd terraform
PROJ=$(terraform output -raw devops_project_id)
BP=$(terraform output -raw build_pipeline_id)
CONN=$(terraform output -raw devops_connection_id)
BRANCH=develop # var.github_branch と同じ値
# Terraform が作ったトリガーを管理から外して削除
terraform state rm oci_devops_trigger.github
oci devops trigger delete --trigger-id "$(terraform output -raw trigger_id)" --force
# CLI で作り直す。レスポンスの "secret" と "trigger-url" を控える
oci devops trigger create-github-trigger \
--project-id "$PROJ" \
--connection-id "$CONN" \
--display-name "handson-functions-devops-github-trigger" \
--actions '[{"type":"TRIGGER_BUILD_PIPELINE","buildPipelineId":"'"$BP"'","filter":{"triggerSource":"GITHUB","events":["PUSH"],"include":{"headRef":"'"$BRANCH"'"}}}]'
# Terraform 管理に戻す(以降 plan に差分が出ないことを確認)
terraform import oci_devops_trigger.github <新しいトリガーのOCID>
terraform plan
控えた trigger-url を Payload URL に、secret を Secret に設定して Webhook を登録し直せば、GitHub の Recent Deliveries が 2xx になります。
6. 自動デプロイと関数動作確認
- リポジトリを
git pushします。 - OCI コンソールの DevOps プロジェクトで、ビルド・パイプラインが自動起動し、Build → Deliver → Trigger Deployment と進むのを確認します。
- 続いてデプロイ・パイプラインが起動し、Deploy Function ステージで関数が新しいイメージへ更新されます。
- 前回同様、関数を実行して確認します。
fn invoke handson-functions-devops-app handson-functions-devops-hello
{"message": "Hello World"}
または OCI CLI からも実行できます。
oci fn function invoke \ --function-id <関数のOCID> \ --file "-" \ --body ''
func.py を書き換えて git push すると、以降はコード変更 → push → 自動ビルド → 自動デプロイが回るようになります。
ハマりどころ
- 502 Failed to pull function image:前回同様、IGW とルートの設定漏れが原因になりがちです。今回は Terraform で作成されるので回避できますが、コンパートメントやサブネットを変えた場合は egress 経路を再確認してください。
- seed イメージが無くて関数が作成できない:OCIR が空だと関数リソースの作成に失敗します。
terraform apply内の seed イメージ push(null_resource)が成功しているか確認してください(docker と OCI 認証トークンが必要)。 - ポリシー不足でビルド/デプロイが失敗:動的グループのマッチ規則やポリシー文が正しいか、コンパートメントを間違えていないか確認します。
- PAT のスコープ不足:プライベートリポジトリは
repo、Webhook 自動登録はadmin:repo_hookが必要です。 - Webhook 未登録で push しても起動しない:手順5.の Webhook 登録を忘れていないか確認します。
400-InvalidParameter: Oracle Linux 7 build runner images are no longer supported.:ビルド・ステージのimageにOL7_X86_64_STANDARD_10を指定すると出ます。OL8_X86_64_STANDARD_10に変更してください。あわせてbuild_spec.yamlのコンテナ・エンジンにも注意(OL8 は Podman)。Logs need to be enabled in order to run the builds. Please enable logs for your project.:DevOps プロジェクトのサービス・ログが未有効です。oci_logging_log_group+oci_logging_log(service = "devops"/category = "all"/resourceにプロジェクト OCID)を作成してください。
最後に
前回は Fn Project CLI を使って「手元から」デプロイしました。本記事では OCI DevOps を使い、GitHub への push を起点に、OCI 側は Terraform を使い構築した CI/CD パイプラインでデプロイしました。
前回記事の締めくくりでも触れたとおり、
手元で気軽に試せる一方、実運用で CI/CD パイプラインを組む場合は OCI DevOps、Infrastructure as Code で管理したい場合は Terraform といった選択肢もあります。
本記事はまさに、その 「OCI DevOps」+「Terraform」を組み合わせた形です。ローカルで感覚を掴む段階を過ぎ、チーム開発や継続的デリバリを見据えるなら、この構成が有力な選択肢になります。
