はじめに
こんにちは、Datadog Japan で Sales Engineer をしている AoTo です。
この投稿は AoTo Advent Calendar 2024 18日目の記事です。
Cloud Run では OS レベルのセキュリティ向上のため、Google が提供しているベースイメージの自動更新が可能です。
本機能によって、Cloud Run にデプロイしているコンテナに Google Cloud の提供するベースイメージを利用することで、ダウンタイム・リビルドなしで48時間以内に脆弱性へ対応するパッチを Google Cloud が適用してくれます。
本機能は Google Cloud Next ’24 で Private Preview として発表され、2024年8月21日に Public Preview となりました。
公式ガイドでは、以下の2つの方法が説明されています。
- アプリケーションソースからのデプロイ
- Dockerfile で
FROM scratch
を利用して一から構築する
本記事では、これらの裏側の仕組みを考察しながら、ベースイメージの自動更新によってどのようなセキュリティが向上するのかを解説します。
Google Cloud Next ’24 で発表された Cloud Run のアップデートは『詳細解説 Cloud Run 最新アップデート』で解説しています🐶
マルチステージビルドとは
まずは、ベースイメージの自動更新の仕組みを理解するためにも、軽量なコンテナイメージビルドの基本となるマルチステージビルド](https://docs.docker.com/build/building/multi-stage/)の仕組みを理解していきましょう。
マルチステージビルドとは、Dockerfile
の可読性・保守性を向上して軽量なコンテナ実行イメージを作成する手法です。特に Go, Rust などのコンパイラ言語の場合、ビルド後のバイナリファイルのみを実行イメージに含めることで、軽量なコンテナイメージが作成・実行できます。
マルチステージビルドを利用しない Dockerfile
とマルチステージビルドを利用した Dockerfile
の差分は以下のようになります。1
# syntax=docker/dockerfile:1
FROM golang:1.23
WORKDIR /src
COPY <<EOF ./main.go
package main
import "fmt"
func main() {
fmt.Println("hello, world")
}
EOF
RUN go build -o /bin/hello ./main.go
+ FROM scratch
+ COPY --from=0 /bin/hello /bin/hello
CMD ["/bin/hello"]
少しわかりづらいので、平易な日本語に直してみましょう。
# おまじない
FROM ビルドするためのイメージ AS 名前
WORKDIR /アプリを置く場所
COPY コードを持ってくる
RUN コンパイルやビルドをする
+ FROM 実行するためのイメージ AS 名前
+ COPY --from=ステージ名 成果物を持ってくる
CMD ["エントリーポイント"]
追加された行は新たにベースイメージを指定し、ビルドされた main.go
を成果物(artifact)としてコピーしています。この時、FROM secatch
を指定すると、イメージに追加のレイヤーは作成されず、ベースイメージは空の状態になります。
こうした FROM scratch
の記載は、例のように事項するバイナリにランタイム依存関係がない場合のみ正常に実行できます。つまり、通常にアプリケーションの場合は、アプリケーションの実行に必要な最小限のベースイメージを指定する必要があります。
つまり、Cloud Run においては Google Cloud の Buildpack ベースイメージを実行イメージとして指定したくなると思います。ですが、ベースイメージの自動更新では、FROM scratch
で作成された実行イメージを指定するだけで良いのです。
その理由を探るためにも、CloudNative Buildpacks(CNB) について知っておきましょう。
CloudNative Buildpacks(CNB) とは
CloudNative Buildpacks(CNB) とは、ソース コードから安全で効率的な、本番環境に対応したコンテナイメージを作成できるツールです。
コンテナイメージの作成は、detect
と build
フェーズを通して行われます。つまり CNB はマルチステージビルドを前提としたツールです。CNB は Builder と呼ばれる、Stack(マルチステージビルドのコンテナイメージ) と Buildpack(Detect
フェーズ・Build
フェーズからなるビルドプロセス)の集合によって定義されます。
そして、これらの Builder は Google Cloud でも gcr.io/buildpacks/builder
が提供されており、Cloud Run, App Engine のソースコードデプロイ時に内部的に利用されています。
詳しくは昨年のブログ『Buildpacks が Google Cloud で使える件』でも解説しています。
つまり、Cloud Run では内部的に Buildpasks が利用されるため、CNB の恩恵を受けたソースコードデプロイとベースイメージの自動更新が可能ということです。
では、ベースイメージの自動更新は CNB のどのような機能を利用しているか考察してみましょう。
pack rebase
とイメージ拡張(image extension)機能
CNB の機能をよく確認してみると、pack rebase
というベースイメージを最新のバージョンに更新できる機能を見つけられます。
この pack rebase
は既存の実行イメージのベースイメージのバージョンを確認し、新しいバージョンが存在するかを判断して存在する場合は実行イメージのメタデータの更新をリビルドなしで実現します。
Cloud Run のベースイメージの自動更新ではダウンタイム・リビルドなしでベースイメージを更新できるため、本機能を利用していることが推測されます。
一方で pack rebase
コマンドを内部的にそのまま実行している可能性は低く、rebase
の機能は基本的にはビルドプロセスを定義する Buildpack に含められます。この Buildpack に追加できる任意の拡張機能がイメージ拡張機能です。
そのため、Google Cloud の Buildpack でも、イメージ拡張機能を利用してランタイムのベースイメージの切り替えを実現していることが推測されます。
こうして、Google Cloud の Buildpack が内部的に存在することでベースイメージの自動更新が実現されます。図のように FROM scratch
を利用したマルチステージビルドで作成されたコンテナ実行イメージは、指定したベースイメージにベースイメージの切り替え(rebase)が行われます。
公式ドキュメントでは FROM scratch
つまり空のベースを実行イメージに指定するように指定しています。ですが、内部的には rebase
が行われるため、Google Cloud が提供するベースイメージをそのまま指定しても問題なく動作します。2
おわりに
Cloud Run のベースイメージの自動更新機能は、Google Cloud により管理されたベースイメージを使用して OS レベルの脆弱性に対するセキュリティの向上が行える機能です。開発者の責任範囲を Google Cloud に以上して、素早い脆弱性の対応を Google Cloud のセキュリティチームに任せることができるのが最大の利点です。
本投稿は、Jagu'e'r クラウドネイティブ分科会で発表した内容を記事にしたものです。Google Cloud のクラウドネイティブ技術に関連する内容の濃いコミュニティにご興味があれば、是非 Jagu'e'r へご参加ください🐯
-
Docker 公式ドキュメント『Multi-stage builds』から引用。この例では Golang の hello world アプリケーションを main.go にそのまま記載しています。 ↩
-
2024年12月時点。Go 1.22 ベースイメージ(
google-22/go122
)で検証。 ↩