今回ありがたいことに業務中にタイトル記載の技術をキャッチアップできる時間を設けてもらったので、その整理のための記事です。
この記事でできるようになること
- Terraformを使用したGCPリソース作成がちょっとできるようになる
- kubectl と kustomizeを使用したGKEクラスターのリソース管理がちょっとできるようになる
- コンテナデプロイがなんとなく分かる
- Workload Identityがちょっとわかる
今回使用するコード
- Django
- Terraform
- k8s
最終的な構成
※ ロードバランサやカスタムドメインは使用しません。
アプリケーションのプロトコルもHTTPです。(HTTPS化はしません)
ローカルでDjangoアプリを確認(startproject, startappの過程は割愛)
- python: 3.9.1
- Django: 4.0.5
- django-environ(.env用)
※ pyenvで仮想環境用意した場合はpip install --upgrade pip
でpipを更新するの忘れないように
クローンしたらdev-app/entrypoint.shに実行権限を付与
cd dev-app
chmod +x entrypoint.sh
コンテナを起動してブラウザからアクセス
docker-compose up -d
http://0.0.0.0:8000 にアクセスして下記画面が表示されればOKです。
マイグレーション実行時、サンプルのテーブルにレコードを1件登録するようにしています。
画面に表示されているexample-user@example.com
はそのテーブルから取得したレコードの値です。
Terraformで各種GCPリソース作成
GCPプロジェクトの作成や、terraform/tfenvのインストール方法などは割愛します
Terraform用のフォルダを作成
mkdir dev-app-tf
Terraformで使用するサービスアカウントを作成
[GCPコンソール] --> [サイドバー] --> [IAMと管理] --> [サービスアカウント]
-
画面上部の「+サービスアカウントを作成」ボタン押下
a. サービスアカウント名:dev-app-tf
b. サービスアカウントID:自動入力
c. サービスアカウントの説明:適当に入力(空でも良い) -
「作成して続行」ボタン押下
-
ロールは下記を選択して「続行」ボタン押下
・ Artifact Registry 管理者
・ Cloud SQL 管理者
・ サービスアカウントの作成
・ Project IAM 管理者
・ Kubernetes Engine Cluster 管理者
・ サービス アカウント ユーザ
・ Compute 閲覧者
・ Secret Manager 管理者
※ ロールは後から着け外しできるので、後続の手順で403エラーが発生したら適宜ロールを見直してください。 -
ユーザロールや管理者ロールも入力せずに「完了」ボタン押下
TerraformでGCPリソースを操作するためのサービスアカウントを作成できました。
-
対象サービスアカウント行の右側にある縦の三点リーダより、「鍵を管理」選択
-
「鍵を追加」ボタン押下
-
「新しい鍵を作成」選択
-
キーのタイプは「JSON」を選択
-
「作成」ボタン押下
JSONファイルがダウンロードされるので、そのファイルをdev-app-tf
フォルダ直下に移動
- このJSONファイルは絶対にGithubをはじめとする公開サイトにアップロードしないでください。不正利用される可能性があります。
環境変数GOOGLE_APPLICATION_CREDENTIALS
に先ほどダウンロードしたJSONキーファイルの絶対パスを設定
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service_account.json
変数定義 および シークレット変数への値割り当て
プロジェクトIDやregionなど、繰り返し記述する可能性がある値を変数定義ファイルvariables.tf
に記述します。
こうすることで、その他tfファイルからvar.project_id
やvar.region
で使用できるようになります。
※ 変数の値割り当てを行うまでもないので今回variables.tfvars
は用意しません。
variable "project_id" {
type = string
default = "{ プロジェクトID }" // 例)cloud-learn-dev
description = "プロジェクトID"
}
variable "region" {
type = string
default = "asia-northeast1"
}
variable "zone" {
type = string
default = "asia-northeast1-a"
}
// シークレット変数
variable "sql_user_password" {
type = string
sensitive = true
}
次にシークレット変数への値割り当てファイルsecret.auto.tfvars
作成します。
※ .gitignoreに記述するなどして、Gitリポジトリへのコミットは回避してください。
sql_user_password = "9!4930g8hjre"
GCPプロビジョニングのためのプロバイダー設定
- terraform.tf と terraform-google.tf, terraform-google-beta.tfを作成
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "4.23.0"
}
google-beta = {
source = "hashicorp/google-beta"
version = "4.23.0"
}
}
}
provider "google" {
project = var.project_id
region = var.region
zone = var.zone
}
provider "google-beta" {
project = var.project_id
region = var.region
zone = var.zone
}
ディレクトリを初期化
初期化を行うことでtfファイルに記述したプロバイダーをダウンロードする。
「Terraform has been successfully initialized!」が出れば初期化完了です。
# dev-app-tf直下で
terraform init
Dockerイメージ管理用のArtifact Registry構成ファイルを作成
- 事前にArtifact Registryを有効化しておいてください([GCPコンソール] --> [サイドバー] --> [Artifact Registry])
今回はdockerイメージをデプロイするのでformatもDOCKERです。
resource "google_artifact_registry_repository" "dev_app__img_repository" {
provider = google-beta
location = var.region
repository_id = "dev-app-img-repository"
format = "DOCKER"
}
applyしましょう。
「Do you want to perform these actions?」と聞かれたらyesと入力してエンターを押下してください。
# dev-app-tfの直下で
terraform apply
「Apply complete! Resources: ...」と表示されたら成功です。
GCPコンソールからArtifact Registryを開くと、リポジトリが作成されていることを確認できます。
データ保存用のCloud SQL構成ファイルを作成
- 事前にCloud SQL Adminを有効化しておいてください([GCPコンソール] --> [サイドバー] --> [SQL])
- 有効にしてから5分ほど待ってterraform apply実行した方がいいかも
// Cloud SQL Admin APIを有効にしておく(5分くらい待った方がいいカモ)
// そもそもSQLインスタンスの生成に時間が掛かるので、applyも時間かかる(15分前後)
resource "google_sql_database" "db_dev_app" {
name = "db-dev-app"
instance = google_sql_database_instance.db_instance_dev_app.id
}
resource "google_sql_database_instance" "db_instance_dev_app" {
name = "db-instance-dev-app"
region = var.region
database_version = "MYSQL_8_0"
settings {
tier = "db-f1-micro"
}
deletion_protection = false
}
resource "google_sql_user" "sql_user_dev_app" {
name = "butterthon"
instance = google_sql_database_instance.db_instance_dev_app.name
password = var.sql_user_password
}
applyしましょう。
完了まで15分くらい掛かります。
# dev-app-tf直下で
terraform apply
GKEクラスタ構成ファイルを作成
- 事前にCloud Resource Manager APIを有効化しておいてください([GCPコンソール] --> [検索テキストボックス] --> [Cloud Resource Managerで検索])
重要なのはworkload_identity_config
とworkload_metadata_config
です。
workload_identityは『k8sサービスアカウントをIAMサービスアカウントと紐づけることで、PodからGCPリソースを操作できるようにする仕組み』のことです。
今回PodからCloudSQLに接続する必要があるため、このworkload_identity
およびworkload_metadata_config
の指定が必須です。
tfファイル内でIAMサービスアカウントを作成
// GKEクラスタのリソース管理で使用するIAMサービスアカウント
resource "google_service_account" "gke_sa_dev_app" {
account_id = "gke-sa-dev-app"
display_name = "gke-sa-dev-app"
}
# 上記IAMサービスアカウントにロールをいくつか付与
# ・Cloud SQL クライアント
# ・Secret Manager のシークレット アクセサー
# ・Artifact Registry 管理者
# ・ストレージオブジェクト閲覧者
resource "google_project_iam_member" "sa_sql_client_dev_app" {
project = var.project_id
role = "roles/cloudsql.client"
member = "serviceAccount:${google_service_account.gke_sa_dev_app.email}"
}
resource "google_project_iam_member" "sa_secret_accessor_dev_app" {
project = var.project_id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.gke_sa_dev_app.email}"
}
resource "google_project_iam_member" "sa_artifact_registry_reader_dev_app" {
project = var.project_id
role = "roles/artifactregistry.reader"
member = "serviceAccount:${google_service_account.gke_sa_dev_app.email}"
}
resource "google_project_iam_member" "sa_storage_viewer_dev_app" {
project = var.project_id
role = "roles/storage.objectViewer"
member = "serviceAccount:${google_service_account.gke_sa_dev_app.email}"
}
/**
* クラスターの作成に10分くらい時間が掛かる
* ノードプールの作成に3分くらい時間が掛かる
*/
resource "google_container_cluster" "cluster_dev_app" {
name = "cluster-dev-app"
location = var.region
remove_default_node_pool = true
initial_node_count = 1
timeouts {
create = "30m"
update = "40m"
}
# Workload Identityを有効化
workload_identity_config {
workload_pool = "${var.project}.svc.id.goog"
}
}
resource "google_container_node_pool" "node_pool_dev_app" {
name = "node-pool-dev-app"
location = var.region
cluster = google_container_cluster.cluster_dev_app.name
node_count = 1
autoscaling {
min_node_count = 0
max_node_count = 1
}
node_config {
workload_metadata_config {
mode = "GKE_METADATA"
}
oauth_scopes = [
"https://www.googleapis.com/auth/devstorage.read_only",
"https://www.googleapis.com/auth/logging.write",
"https://www.googleapis.com/auth/monitoring",
"https://www.googleapis.com/auth/service.management.readonly",
"https://www.googleapis.com/auth/servicecontrol",
"https://www.googleapis.com/auth/trace.append"
}
}
applyしましょう。
これまた完了するのに20分くらい掛かります。
# dev-app-tf直下で
terraform apply
これでGCPリソースの作成は完了です。
実際にコンテナデプロイ作業に入っていきましょう。
DjangoアプリのDockerイメージをビルド&プッシュ
gcloudコマンドのインストール方法などは割愛します
イメージ
ビルド&プッシュ
下記コマンドでDockerイメージをビルドしてArtifact Registryにプッシュします。
# dev-app直下で
gcloud builds submit --tag asia-northeast1-docker.pkg.dev/cloud-learn-dev/dev-app-img-repository/sample-image:latest
# gcloud builds submit --tag { リージョン }-docker.pkg.dev/{ プロジェクトID }/{ Artifact RegistryのリポジトリID }/{ 任意のイメージ名称 }:{ タグ }
Artifact Registryのリポジトリを見てみると、DockerイメージがPUSHされていることが確認できます。
これでコンテナデプロイの準備完了です。
kubectl と kustomizeでGKEクラスターにリソースを作成
リソースの作成は、k8sの構成ファイルカスタマイズツールであるkustomizeを使用します。
kustomizeにはbased(ベース)
とoverlays(オーバーレイ)
の概念があります。
base
・・・リソースのセット(deployment.ymlやservice.yml)とkustomization.ymlを含むディレクトリ
overlays
・・・baseを参照するkustomization.ymlを含むディレクトリ
.
├── base
│ ├── deployment.yml
│ ├── service.yml
│ └── kustomization.yml
│
└── overlays
├── staging
│ └── kustomization.yml
│
└── production
└── kustomization.yml
kustomizeを使うと、overlays(デプロイ環境)ごとの共通部分をbaseとして切り出すことができるようになります。
また、production環境だけPod数を10個にするといったoverlaysごとのカスタマイズも可能です。
kustomizeを使わない場合、環境ごとに似たようなymlファイルを複製し、修正が発生した場合は全てのymlに同じような修正を入れる作業が発生したりします。
kustomizeはそんな面倒も解消してくれるツールです。
クラスター認証
kustomizeを使用してGKEクラスタのリソースを管理するためクラスタ認証を行います
# 一応dev-app-k8sの直下で
gcloud container clusters get-credentials cluster-dev-app --zone asia-northeast1
# gcloud container clusters get-credentials { Terraformで作成したGKEクラスタの名前 } --zone asia-northeast1
#
# 以下が表示されればOK
# Fetching cluster endpoint and auth data.
# kubeconfig entry generated for cluster-dev-app.
k8sサービスアカウント作成
前述の通り、k8sサービスアカウントはIAMサービスアカウントとは別物です。
なので、IAMサービスアカウントの権限を借用できるようk8sサービスアカウントをTerraformで作成したIAMサービスアカウントに関連づけます。
それがannotations
です。
apiVersion: v1
kind: ServiceAccount
metadata:
name: gke-sa-dev-app
annotations:
iam.gke.io/gcp-service-account: gke-sa-dev-app@cloud-learn-dev.iam.gserviceaccount.com
一旦k8sサービスアカウントだけ作成します。
# dev-app-k8sの直下で
kubectl apply -f overlays/staging/sidecar/gke_service_account.yml
サービスアカウントが作成されたかkubectl get sa
で確かめます。
指定した名前でサービスアカウントが作られていればOKです。
kubectl get sa
NAME SECRETS AGE
default 1 145m
gke-sa-dev-app 1 38s
しかし、annotationsで関連付けただけではIAMサービスアカウントの権限借用はできません。
k8sサービスアカウントとIAMサービスアカウントの間にIAMポリシーバインディング
を追加します。
これでようやくk8sサービスアカウントがIAMサービスアカウントの権限を借用できるようになります。
# 一応dev-app-k8sの直下で
gcloud iam service-accounts add-iam-policy-binding \
> gke-sa-dev-app@cloud-learn-dev.iam.gserviceaccount.com \
> --role="roles/iam.workloadIdentityUser" \
> --member="serviceAccount:cloud-learn-dev.svc.id.goog[default/gke-sa-dev-app]"
# gcloud iam service-accounts add-iam-policy-binding \
# > { Terraformで作成したIAMサービスアカウントのメールアドレス } \
# > --role roles/iam.workloadIdentityUser \
# > --member "serviceAccount:{ プロジェクトID }.svc.id.goog[{ NAMESPACE }/{ k8sサービスアカウントの名前(kubectl get saで確認可能) }]"
GCPコンソールから、IAMサービスアカウントの権限を見るとk8sサービスアカウントポリシーバインディングされていることが確認できます。
軽い用語説明
kustomizeのマニフェストを作成する前に軽く用語の説明を入れておきます。
-
クラスター
-
マスターノード
とワーカーノード
で構成されたk8sシステムのこと
-
-
マスターノード
-
ワーカーノード
上のコンテナを管理するだけ - マニフェストの内容でワーカーノードを管理してくれる
- 開発者がマニフェストを変更すると、その内容を勝手にワーカーノードで反映してくれる
-
-
ワーカーノード
- 実際にコンテナが稼働する場所(サーバーに相当するもの)
- ワーカーノードの調整はマスターノードの仕事なので、開発者が直接操作することはほとんどない
-
Deployment
-
Pod
のデプロイ管理 - 障害などでPodが停止してしまった場合は自動でPodを増やしたりなど、マニフェストに記載された状態を保つ動きをしてくれます(厳密にはPodの増減はReplicaSetが実施します)
- マニフェストファイルのPod数を書き換えたら、それに従ってPodの数を勝手に増減してくれます
-
-
Service
-
ワーカーノード
内のPod
にアクセスを振り分けるロードバランサーみたいなもの - あくまで
ワーカーノード
内のPod
に振り分けるだけなので、それぞれのワーカーノードへのアクセス振り分けは本物のロードバランサーなどを使用する必要があります
-
各種baseファイルを作成
.
├── base
│ ├── deployment.yml
│ ├── service.yml
│ └── kustomization.yml
│
└── overlays
...
- deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: dev-app
spec:
replicas: 3
template:
metadata:
name: dev-app
spec:
serviceAccountName: overlaysごとに書き換える
containers:
- name: dev-app-pod
image: asia-northeast1-docker.pkg.dev/cloud-learn-dev/dev-app-img-repository/sample-image:latest
imagePullPolicy: Always
env:
- name: DEBUG
value: "True"
- name: DATABASE_URL
value: "mysql://{ Terraformで作成したSQLユーザ}:{ Terraformで作成したSQLユーザパスワード }@127.0.0.1:3306/db-dev-app?charset=utf8mb4"
- name: DJANGO_SECRET_KEY
value: "django-insecure-zgxe_knf8as%_$o%jq$4yw@i5p6f0d8p74&a!avsuvfctu+)(c"
- name: DJANGO_ALLOWED_HOSTS
value: "*"
- name: DJANGO_SETTINGS_MODULE
value: config.settings.dev
ports:
- containerPort: 8000
- name: cloud-sql-proxy
image: gcr.io/cloudsql-docker/gce-proxy:1.28.0
command:
- overlaysごとに書き換える
securityContext:
runAsNonRoot: true
- service.yml
apiVersion: v1
kind: Service
metadata:
name: dev-app
spec:
type: LoadBalancer
ports:
- port: 80 # Serviceのポート
targetPort: 8000 # Podの内部ポート(gnicornで0.0.0.0:8000にバインドしているので、targetPortも8000)
protocol: TCP # 通信に使うプロトコル
nodePort: 30000 # ワーカーノードのポート(30000~32767の間であればなんでも良い)
- kustomization.yml
resources:
- ./deployment.yml
- ./service.yml
各種overlaysファイル作成
.
├── base
│ ├── ...
│ ├── ..
│ └── .
│
└── overlays
└── staging
├── sidecar
│ └─ gke_service_account.yml
|
├── patches
│ └── deployment.yml
│
└── kustomization.yml
見慣れないpatchesディレクトリがあります(ディレクトリ名はpatchesでなくても良い)。
これはbaseのdeployment.ymlをstaging用に書き換えるためのものです。
- deployment.yml
- baseのdeployment.ymlでstaging用に書き換えたいところだけ修正します。
apiVersion: apps/v1
kind: Deployment
metadata:
name: dev-app
spec:
template:
spec:
serviceAccountName: stg-gke-sa-dev-app-001 # Terraformで作成したGKEアカウント
containers:
- name: cloud-sql-proxy
command:
- "/cloud_sql_proxy"
- "-log_debug_stdout"
- "-instances=cloud-learn-dev:asia-northeast1:db-instance-dev-app=tcp:0.0.0.0:3306" # -instances={ CloudSQLの接続名 }=tcp:0.0.0.0:3306
- "-term_timeout=30s"
- kustomization.yml
bases:
- ../../base
namePrefix: "stg-"
nameSuffix: "-001"
commonLabels:
stg: dev-app
patches:
- ./patches/deployment.yml
bases | baseディレクトリへの相対パス(baseディレクトリ直下のkustomization.ymlを参照する) |
namePrefix | 全てのmetadata.name(1階層目に限る)に接頭辞を付与 |
nameSuffix | 全てのmetadata.name(1階層目に限る)に接尾辞を付与 |
commonLabels | 全てのリソースにラベルを設定する。 このラベル付けが個人的に超便利です。 matchLabelsやセレクターラベル も付けてくれるので、ラベル付けを意識する必要もなければ、ラベル付けを忘れてデプロイがうまくいかないという凡ミスも防げます。 |
この状態でkubectl kustomize ./overlays/staging
を実行して構成ファイルをコンソール出力してみます。
出力結果を確認すると、baseの内容がoverlays/patchesの内容で書き換えられていることが分かります。
出力結果
apiVersion: v1
kind: Service
metadata:
labels: # commonLabelsが反映されている
stg: dev-app # commonLabelsが反映されている
name: stg-dev-app-001 # { overlaysのnamePrefix } + { baseのService名 } + { overlaysのnameSuffix }
spec:
ports:
- nodePort: 30000
port: 80
protocol: TCP
targetPort: 8000
selector: # commonLabelsが反映されている
stg: dev-app # commonLabelsが反映されている
type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels: # commonLabelsが反映されている
stg: dev-app # commonLabelsが反映されている
name: stg-dev-app-001 # { overlaysのnamePrefix } + { baseのDeployment名 } + { overlaysのnameSuffix }
spec:
replicas: 3
selector: # commonLabelsが反映されている
matchLabels: # commonLabelsが反映されている
stg: dev-app # commonLabelsが反映されている
template:
metadata:
labels: # commonLabelsが反映されている
stg: dev-app # commonLabelsが反映されている
name: dev-app
spec:
containers:
- command: # overlaysのcommandで上書きされている
- /cloud_sql_proxy
- -log_debug_stdout
- -instances=cloud-learn-dev:asia-northeast1:db-instance-dev-app=tcp:0.0.0.0:3306
- -term_timeout=30s
image: gcr.io/cloudsql-docker/gce-proxy:1.28.0
name: cloud-sql-proxy
securityContext:
runAsNonRoot: true
- env:
- name: DEBUG
value: "True"
- name: DATABASE_URL
value: mysql://{ Terraformで作成したSQLユーザ}:{ Terraformで作成したSQLユーザパスワード }@127.0.0.1:3306/db-dev-app?charset=utf8mb4
- name: DJANGO_SECRET_KEY
value: django-insecure-zgxe_knf8as%_$o%jq$4yw@i5p6f0d8p74&a!avsuvfctu+)(c
- name: DJANGO_ALLOWED_HOSTS
value: '*'
image: asia-northeast1-docker.pkg.dev/cloud-learn-dev/dev-app-img-repository/sample-image:latest
imagePullPolicy: Always
name: dev-app
ports:
- containerPort: 8000
serviceAccountName: gke-sa-dev-app # overlaysのserviceAccountNameで上書きされている
GKEクラスターにデプロイ
遂にデプロイです。
kubectl apply --kustomize ./overlays/staging
# kubectk apply --kustomize { デプロイしたいoverlaysディレクトリパス }
#
# 以下が表示されれば一旦OK
# service/stg-dev-app-001 created
# deployment.apps/stg-dev-app-001 created
GCPコンソールよりGKEクラスターを確認してみます。
-
ワークロード(Deployment)
-
ServiceとIngress
-
上記Serviceを選択してPodを確認
では、Serviceのエンドポイントにブラウザからアクセスしてみます。
無事アクセスできました。
画面に表示されているexample-user@example.com
はサンプルのテーブルから取得したレコードのデータなので、CloudSQLとの接続もできています。
ただ、k8sのマニフェストファイルの環境変数設定部分がイケてないです。
このままではシークレット情報をGithubに誤コミットもしくは、デプロイ前にシークレット情報を書き換え、デプロイ後に元に戻す手間が発生しそうです。
なので、シークレット情報はGCPのSecretManagerで管理するようにしましょう。
SecretManagerでシークレット情報を管理
TerraformにSecretManagerの構成ファイル追加
resource "google_secret_manager_secret" "staging_dev_app_debug" {
project = var.project_id
secret_id = "staging-dev-app_DEBUG"
replication {
user_managed {
replicas { location = var.region }
}
}
labels = { label = "staging" }
}
resource "google_secret_manager_secret" "staging_dev_app_django_settings_module" {
project = var.project_id
secret_id = "staging-dev-app_DJANGO_SETTINGS_MODULE"
replication {
user_managed {
replicas { location = var.region }
}
}
labels = { label = "staging" }
}
resource "google_secret_manager_secret" "staging_dev_app_django_secret_key" {
project = var.project_id
secret_id = "staging-dev-app_DJANGO_SECRET_KEY"
replication {
user_managed {
replicas { location = var.region }
}
}
labels = { label = "staging" }
}
resource "google_secret_manager_secret" "staging_dev_app_database_url" {
project = var.project_id
secret_id = "staging-dev-app_DATABASE_URL"
replication {
user_managed {
replicas { location = var.region }
}
}
labels = { label = "staging" }
}
resource "google_secret_manager_secret" "staging_dev_app_django_allowed_hosts" {
project = var.project_id
secret_id = "staging-dev-app_DJANGO_ALLOWED_HOSTS"
replication {
user_managed {
replicas { location = var.region }
}
}
labels = { label = "staging" }
}
resource "google_secret_manager_secret_version" "v_staging_dev_app_debug" {
secret = google_secret_manager_secret.staging_dev_app_debug.id
secret_data = "False"
}
resource "google_secret_manager_secret_version" "v_staging_dev_app_django_settings_module" {
secret = google_secret_manager_secret.staging_dev_app_django_settings_module.id
secret_data = "config.settings.prd"
}
resource "google_secret_manager_secret_version" "v_staging_dev_app_django_secret_key" {
secret = google_secret_manager_secret.staging_dev_app_django_secret_key.id
secret_data = "django-insecure-zgxe_knf8as%_$o%jq$4yw@i5p6f0d8p74&a!avsuvfctu+)(c"
}
resource "google_secret_manager_secret_version" "v_staging_dev_app_database_url" {
secret = google_secret_manager_secret.staging_dev_app_database_url.id
secret_data = "mysql://${google_sql_user.sql_user_dev_app.name}:${var.sql_user_password}@127.0.0.1:3306/${google_sql_database.db_dev_app.name}?charset=utf8mb4"
}
resource "google_secret_manager_secret_version" "v_staging_dev_app_django_allowed_hosts" {
secret = google_secret_manager_secret.staging_dev_app_django_allowed_hosts.id
secret_data = "*"
}
SecretManagerの構成ファイルを指定してapply
普通にapplyしちゃうとGKEクラスタが再作成されちゃって20分くらい待つハメになります(GKEクラスタ構成ファイルのオプションで解決できるのかな)。
また、terraformコマンドにファイル単位でapplyするオプションがないのでshellコマンドで解決しています。
terraform apply `cat ./secret-manager.tf | terraform fmt - | grep -E 'resource |module ' | tr -d '"' | awk '{printf("-target=%s.%s ", $2,$3);}'`
#
# 1つずつ地道にapplyすることも可能です
# terraform apply -target="google_secret_manager_secret.staging_dev_app_debug"
# terraform apply -target="google_secret_manager_secret.staging_dev_app_django_settings_module"
# terraform apply -target="..."
# ...
# ..
# .
SecretManagerを開いて、構成ファイル通りに登録されているか確認しましょう。
[GCPコンソール] --> [サイドバー] --> [セキュリティ] --> [Secret Manager]
External Secretsを使用してSecretManagerの値を読み込む
External Secretsは、k8s用の外部シークレット読み込みツールです。
AWS Secrets Manager、HashiCorp Vault、Google Secrets Manager、 AzureKeyVaultなどの外部シークレット管理システムを統合するKubernetesオペレーターです。オペレーターは外部APIから情報を読み取り、その値をKubernetesシークレットに自動的に挿入します。
Helm(k8s用のパッケージマネージャ)を使用してExternal Secretsをインストールします
# dev-app-k8s直下で
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets
base直下に構成ファイルexternal-secret.yml
を作成してSecretStore
とExternalSecret
を記述します。
リソース名(kind) | 役割 |
---|---|
SecretStore | 外部シークレットとの認証方法を指定します。 |
ExternalSecret | 外部シークレットからフェッチするものを指定します。 |
(当記事では使わないけど) ClusterSecretStore |
GKEクラスタ内にある全ての名前空間から参照できるグローバルなSecretStore。 1つのGKEクラスタに別名前空間 かつ SecretStoreを使用するようなアプリをデプロイする場合はSecretStoreではなくClusterSecretStoreを使った方が良さそうですね。 |
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: secretstore
spec:
provider:
gcpsm:
projectID: cloud-learn-dev
auth:
workloadIdentity:
clusterLocation: asia-northeast1
clusterName: cluster-dev-app
serviceAccountRef:
name: overlaysごとに書き換え
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: external-secret
spec:
refreshInterval: 1h
secretStoreRef:
kind: SecretStore
name: secretstore
target:
name: overlaysごとに書き換え
creationPolicy: Owner
data:
- secretKey: DATABASE_URL
remoteRef:
key: overlaysごとに書き換え(Secret Manager上の名前を指定)
version: latest
- secretKey: DEBUG
remoteRef:
key: overlaysごとに書き換え(Secret Manager上の名前を指定)
version: latest
- secretKey: DJANGO_SECRET_KEY
remoteRef:
key: overlaysごとに書き換え(Secret Manager上の名前を指定)
version: latest
- secretKey: DJANGO_SETTINGS_MODULE
remoteRef:
key: overlaysごとに書き換え(Secret Manager上の名前を指定)
version: latest
- secretKey: DJANGO_ALLOWED_HOSTS
remoteRef:
key: overlaysごとに書き換え(Secret Manager上の名前を指定)
version: latest
base直下のkustomization.ymlにも追記します。
resources:
- ./deployment.yml
- ./service.yml
- ./external-secret.yml # 追記
overlaysにもexternal-secret.ymlを作って該当箇所を書き換えましょう
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: secretstore
spec:
provider:
gcpsm:
projectID: cloud-learn-dev
auth:
workloadIdentity:
clusterLocation: asia-northeast1
clusterName: cluster-dev-app
serviceAccountRef:
name: gke-sa-dev-app # k8sサービスアカウント名称
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: external-secret
spec:
secretStoreRef:
name: stg-secretstore-001 # SecretStoreのmetadata.name
target:
name: stg-secrets
data:
- secretKey: DATABASE_URL
remoteRef:
key: staging-dev-app_DATABASE_URL
- secretKey: DEBUG
remoteRef:
key: staging-dev-app_DEBUG
- secretKey: DJANGO_SECRET_KEY
remoteRef:
key: staging-dev-app_DJANGO_SECRET_KEY
- secretKey: DJANGO_SETTINGS_MODULE
remoteRef:
key: staging-dev-app_DJANGO_SETTINGS_MODULE
- secretKey: DJANGO_ALLOWED_HOSTS
remoteRef:
key: staging-dev-app_DJANGO_ALLOWED_HOSTS
overlaysのkustomization.ymlにも追記します。
bases:
- ../../base
...省略...
patches:
- ./patches/deployment.yml
- ./patches/external-secret.yml # 追記
最後に、baseとoverlaysにあるdeployment.ymlのenv
を修正します。
apiVersion: apps/v1
kind: Deployment
metadata:
name: dev-app
spec:
replicas: 3
template:
metadata:
name: dev-app
spec:
...省略...
containers:
- name: dev-app-pod
...省略...
env:
- name: DEBUG
valueFrom:
secretKeyRef:
name: overlaysごとに書き換える
key: DEBUG # ExternalSecretのdata.secretKey
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: overlaysごとに書き換える
key: DATABASE_URL # ExternalSecretのdata.secretKey
- name: DJANGO_SECRET_KEY
valueFrom:
secretKeyRef:
name: overlaysごとに書き換える
key: DJANGO_SECRET_KEY # ExternalSecretのdata.secretKey
- name: DJANGO_ALLOWED_HOSTS
valueFrom:
secretKeyRef:
name: overlaysごとに書き換える
key: DJANGO_ALLOWED_HOSTS # ExternalSecretのdata.secretKey
- name: DJANGO_SETTINGS_MODULE
valueFrom:
secretKeyRef:
name: overlaysごとに書き換える
key: DJANGO_SETTINGS_MODULE # ExternalSecretのdata.secretKey
ports:
- containerPort: 8000
- name: cloud-sql-proxy
image: gcr.io/cloudsql-docker/gce-proxy:1.28.0
command:
- overlaysごとに書き換える
securityContext:
runAsNonRoot: true
apiVersion: apps/v1
kind: Deployment
metadata:
name: dev-app
spec:
template:
spec:
serviceAccountName: gke-sa-dev-app
containers:
### ここから修正した部分 ###
- name: dev-app-pod
env:
- name: DEBUG
valueFrom:
secretKeyRef:
name: stg-secrets # ExternalSecretのtarget.name
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: stg-secrets # ExternalSecretのtarget.name
- name: DJANGO_SECRET_KEY
valueFrom:
secretKeyRef:
name: stg-secrets # ExternalSecretのtarget.name
- name: DJANGO_ALLOWED_HOSTS
valueFrom:
secretKeyRef:
name: stg-secrets # ExternalSecretのtarget.name
- name: DJANGO_SETTINGS_MODULE
valueFrom:
secretKeyRef:
name: stg-secrets # ExternalSecretのtarget.name
### 修正ここまで
...省略...
applyします
# apply
kubectl apply -k ./overlays/staging
applyしただけではPodが更新されないので、Deploymentを再起動します
(ダウンタイムなしでPodを刷新してくれます)
kubectl rollout restart deploy stg-dev-app-001
# kubectl rollout restart deploy { Deploymentの名前 }
Djangoがprd用の設定ファイルを読み込むよう、SecretManagerのDJANGO_SETTINGS_MODULE
の値をsettings.config.prd
としたので、ログに■■■■■■ prd
と出力されているはずです。
Podのログを確認してみます。
[GCPコンソール] --> [サイドバー] --> [Kubernetes Engine] --> [ServiceとIngress] より Service名選択 --> [処理元のPod]より適当なPodを選択 --> [コンテナ]よりdev-app-podの「ログを表示」を選択
以上です。
参考情報