1.はじめに: Kubernetes Secretが抱える問題
-
SecretはKubernetesにおいて機密情報(DBパスワードやAPIキー)を扱うためのリソース。
-
しかし、base64エンコードで格納されるだけで暗号化はされていない。
-
Gitでのバージョン管理やCI/CDツールとの連携時に、平文のSecretが漏洩するリスクがある。
-
解決策として、SOPS(Secrets OPerationS)を使ってマニフェストそのものを暗号化し、安全に管理・運用する方法を紹介。
これらの問題を解決するために、本記事では以下の方法を紹介します:
✅ SOPS(Secrets OPerationS)を活用し、secretマニフェスト全体を暗号化
✅ GitLabに安全にpushし、CI/CD経由で復号 → 運用までを自動化
2. 🎯 この記事のゴール
SecretをGitLabに安全に組み込み、CI/CDパイプラインでGKEに自動デプロイするまでの一連のセキュアなワークフローを構築。
具体的には以下の構成でクラウドネイティブなToDoアプリを実装・運用。
[GitLab]
|
ー[GKE]ーーーーーーーーーーーーーーーーーーーーーーーーー
| |
|[Internet] |
| | |
|[ LoadBalancer Service ] |
| | |
|[ Node.js アプリ ] |
| | |
|[ Kubernetes Secret ] (DB接続情報など) |
| | |
|[ PostgreSQL StatefulSet ] ── [ PersistentVolume ] |
| | |
|[ ConfigMap (SQL定義) ] |
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
各コンポーネント技術:
コンポーネント | 技術 |
---|---|
クラスタ | GKE(Google Kubernetes Engine) |
アプリケーション | Node.js(ExpressベースREST API) |
データベース | PostgreSQL(StatefulSet+PV) |
Secret暗号化 | SOPS(GCP KMSと統合) |
CI/CDパイプライン | GitLab CI/CD(マニフェストapply) |
3. ⚙️ 実装
本記事では、クラウドネイティブなToDoアプリケーションを「アプリ開発」「Kubernetesマニフェスト作成」「SOPSによるSecretの安全管理」「GitLab CI/CDによるGKEデプロイ」という本番環境に即した構成で一貫して構築していきます。
(GitHubに必要なディレクトリとファイルは公開してあります。)
https://github.com/Elie314159265/secure_secret_with_sops
3.1 必要なディレクトリとファイルを作成
今回のディレクトリ構成は以下のようになります。GCPで以下のファイルとディレクトリを作成してください。(ディレクトリやファイル名は適切に変更してOK)
k8s_secure_secret_resource_with_sops
|
|ーnodejs-app/
| |ーDockerfile # アプリをDockerイメージとしてコンテナ化するための定義
| |ーindex.js # Expressで構築されたメインのAPIロジック
| |ーdb.js # PostgreSQL接続設定
| |ーpackage.json # Node.jsプロジェクトの依存管理と実行定義
| |ー.env.sample # 環境変数の定義ファイル
|
|ーk8s/
| ├── deployment.yaml # Node.jsアプリのDeployment
| ├── postgres-init-sql.yaml # 初期SQLのConfigMap
| ├── (postgres-secret.enc.yaml) # 暗号化されたSecret。後に作成します
| ├── postgres-secret.yaml # 復号後の平文Secret(実際にはGitに含めな|い)
| ├── postgres-service.yaml # PostgreSQL用のService(Headless)
| ├── postgres-statefulset.yaml # PostgreSQLのStatefulSet
| └── service.yaml # Node.jsアプリ用のService
|
|ー.gitignore # コミットしないファイル名を追加
|ー.sops.yaml #
|ー.gitlab-ci.yml # GitLab CI ファイル。自動デプロイ情報定義
上記ファイルのそれぞれのテンプレートを示します。
🐳 Dockerfile:アプリをDockerで実行するための定義
FROM node:20-alpine
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install --production
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
✅ 解説:
-
ベースイメージ:node:20-alpine は軽量でセキュアなNode.jsランタイム。
-
作業ディレクトリ:/usr/src/app にコードを配置。
-
依存解決:npm install --production により、本番用の依存のみをインストール。
-
CMD:Node.js アプリを npm start で起動(これは後述の package.json に定義されている)。
index.js:ExpressベースのREST APIロジック
const express = require('express');
const bodyParser = require('body-parser');
const db = require('./db');
const app = express();
const port = 3000;
✅ エンドポイント機能:
-
GET /todos:全ToDo取得
-
POST /todos:新規ToDo登録
-
DELETE /todos/:id:ToDo削除
✅ 特徴:
-
body-parserでJSONのPOSTボディを受け取れるようにしている。
-
pgのラッパーであるdb.jsを使って、SQLを実行。
-
簡潔な構成だが、REST API設計・Kubernetesデプロイにも適した構造。
db.js:PostgreSQLとの接続モジュール
const { Pool } = require('pg');
const pool = new Pool({
host: process.env.DB_HOST || 'localhost',
port: process.env.DB_PORT || 5432,
user: process.env.DB_USER || 'node_user',
password: process.env.DB_PASSWORD || 'password',
database: process.env.DB_NAME || 'node_app_db',
});
module.exports = {
query: (text, params) => pool.query(text, params),
};
✅ 解説:
-
pg モジュールの Pool クラスを使って、PostgreSQLへのコネクションプールを構築。
-
.env や Kubernetesの Secret 経由で環境変数から設定値を取得。
-
query() メソッドを提供し、SQLを実行可能なシンプルなDBインターフェースとして外部ファイルで使えるように設計。
package.json:Node.jsアプリのメタ情報と依存定義
{
"name": "todo-api",
"version": "1.0.0",
"description": "Simple ToDo API with Express and PostgreSQL",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"body-parser": "^1.20.2",
"express": "^4.18.2",
"pg": "^8.11.3"
}
}
✅ 解説:
-
name, version, description:プロジェクトの基本情報
-
main:アプリのエントリポイント
-
scripts.start:npm start で index.js を実行
-
dependencies:使用しているnpmパッケージ
.env.sample: 環境変数構成例を示すテンプレート
# PostgreSQL settings
DB_HOST=your-db-host
DB_PORT=1000
DB_USER=your-username
DB_PASSWORD=your-password
DB_NAME=your-database
✅ 解説:
- この .env.sample は 環境変数のテンプレートファイル
- Git管理対象にしておき、実際の .env は .gitignore で除外しておくのがベストプラクティス
- あくまで雛形、参考用なので実際にこの変数が使われるわけではない。
- どの環境変数を定義すべきかを明示するためのファイル
✅ 各変数の説明
変数名 | 用途 |
---|---|
DB_HOST |
PostgreSQL サーバのホスト名。K8s内では Service 名 postgres を参照。 |
DB_PORT |
PostgreSQL のポート番号。デフォルトは 5432 。 |
DB_USER |
DB接続に使うユーザー名。例:node_user
|
DB_PASSWORD |
DB接続パスワード。セキュアに管理する必要あり |
DB_NAME |
使用するデータベース名。例:node_app_db
|
deployment.yaml:Node.jsアプリ用 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: nodejs-app
labels:
app: nodejs-app
spec:
replicas: 2
selector:
matchLabels:
app: nodejs-app
template:
metadata:
labels:
app: nodejs-app
spec:
containers:
- name: nodejs
image: <registoryユーザ名>/todo-app:latest #DockerHubのユーザ名に置き換える
ports:
- containerPort: 3000
envFrom:
- secretRef:
name: postgres-secret
🔍 ポイント
-
Node.js アプリのPodを2つ起動
-
PostgreSQL接続情報を Secret から注入
-
LoadBalancer Serviceとセットで外部公開が可能
postgres-init-sql.yaml:初期SQLを提供する ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-init-sql
data:
init.sql: |
CREATE TABLE IF NOT EXISTS todos (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
done BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
🔍 ポイント
-
PostgreSQL起動時に実行される初期化SQL(/docker-entrypoint-initdb.d/init.sql)を定義
-
StatefulSet の initContainers によりPod起動時にコピーされる
postgres-secret.yaml:暗号化前のK8s Secret(Git非公開)
apiVersion: v1
kind: Secret
metadata:
name: postgres-secret
type: Opaque
stringData:
DB_HOST: postgres
DB_PORT: "5432"
DB_USER: node_user
DB_PASSWORD: password
DB_NAME: node_app_db
POSTGRES_USER: node_user
POSTGRES_PASSWORD: password
POSTGRES_DB: node_app_db
🔍 ポイント
-
sops -d postgres-secret.enc.yaml > postgres-secret.yaml によって生成される一時ファイル(復号化)
-
Gitには含めない(.gitignore)
-
kubectl apply -f によって Deployment や StatefulSet で参照される
-
Gitにそのまま Secret マニフェストをpushすると機密情報漏洩リスクがあるのでセキュアなCI/CDパイプラインでもSecretの復号処理が必要
🔐 SOPS(Secrets OPerationS) の導入により以下が可能になります:
項目 | 内容 |
---|---|
暗号化対象 |
stringData や data フィールドの値のみ |
対応KMS | GCP KMS / AWS KMS / PGP などに対応(今回はGCP KMSを利用) |
Git管理 |
.enc.yaml として安全にリポジトリ管理可能
|
CI/CD対応 | GitLabやGitHub Actionsで復号→apply できる |
postgres-service.yaml:PostgreSQL用 Headless Service
yaml
apiVersion: v1
kind: Service
metadata:
name: postgres
spec:
clusterIP: None # <- Headlessにする
selector:
app: postgres
ports:
- port: 5432
🔍 ポイント
-
StatefulSet のためのHeadless Service
-
DNSレコード形式が podname.service.namespace.svc.cluster.local に
-
Podごとに異なるストレージやSQL初期化を行うために必要
postgres-statefulset.yaml:PostgreSQLのStatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:15-alpine
ports:
- containerPort: 5432
env:
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
envFrom:
- secretRef:
name: postgres-secret
volumeMounts:
- name: pgdata
mountPath: /var/lib/postgresql/data
- name: init-sql
mountPath: /docker-entrypoint-initdb.d
initContainers:
- name: init-sql-loader
image: busybox
command: [sh, -c, 'cp /init.sql /docker-entrypoint-initdb.d/init.sql']
volumeMounts:
- name: init-sql-config
mountPath: /init.sql
subPath: init.sql
- name: init-sql
mountPath: /docker-entrypoint-initdb.d
volumes:
- name: init-sql
emptyDir: {}
- name: init-sql-config
configMap:
name: postgres-init-sql
volumeClaimTemplates:
- metadata:
name: pgdata
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 5Gi
🔍 ポイント
-
永続ボリューム(PV)付きPostgreSQLインスタンス
-
initContainers により init.sql を /docker-entrypoint-initdb.d/ に配置
-
データは永続化され、Pod再起動時も保持される
-
StatefulSet+PVで永続化かつ信頼性のあるDB
(通常のDeploymentはPodが再作成されるたびにストレージは破棄されるがStatefulSetを使うことでデータを永続化することができ、またpostgres-0, postgres-1, ... のように一意な名前が割り当てることができる。)
service.yaml:Node.jsアプリ用のService
apiVersion: v1
kind: Service
metadata:
name: nodejs-service
spec:
type: LoadBalancer
selector:
app: nodejs-app
ports:
- port: 80
targetPort: 3000
🔍 ポイント
-
外部向けアクセス用のエントリーポイント
-
GKEでクラウドのLB(例: GCLB)をプロビジョニング
-
内部の 3000 ポートを外部の 80 にマッピング
✅ k8s/のファイルのまとめ
ファイル名 | 目的 | ポイント |
---|---|---|
deployment.yaml |
Node.jsアプリのデプロイ | Secretと連携、レプリカ数管理 |
postgres-init-sql.yaml |
SQL初期化 | ConfigMap + InitContainer |
postgres-secret.enc.yaml |
暗号化Secret(SOPS) | Git管理OK、CI/CDで復号 |
postgres-secret.yaml |
平文Secret | Git管理NG、CI/CD生成用 |
postgres-service.yaml |
Headless Service | StatefulSet用のDNS解決 |
postgres-statefulset.yaml |
PostgreSQL本体 | 永続化、初期化処理付き |
service.yaml |
外部公開Service | LoadBalancer(またはNodePort) |
.sops.yaml:SOPS による 自動暗号化ルール を定義するための設定ファイル
creation_rules:
- kms: projects/${PROJECT_ID}/locations/global/keyRings/secret_key/cryptoKeys/secret_key
encrypted_regex: '^(data|stringData)$'
path_regex: '.*\.yaml$'
✅ 各項目の解説
項目 | 意味 |
---|---|
creation_rules |
SOPSが新しく暗号化する際に適用するルールのリスト(配列) |
kms |
GCPのKMSキーを使うことを指定。 これによりGCPプロジェクト内のKMS鍵で暗号化/復号される。 |
encrypted_regex |
マニフェスト中のどのキーの値を暗号化するかを正規表現で指定。ここでは data または stringData の中身だけ暗号化対象。 |
path_regex |
どのファイル名にこのルールを適用するか。ここでは .yaml 拡張子の全ファイル。 |
.gitignore: Gitで追跡させたくないファイルやディレクトリを明示的に除外するためのファイル
.env
k8s/postgres-secret.yaml
.gitlab-ci.yml: GitLab CI/CD を使って GKE(Google Kubernetes Engine)に安全かつ自動的にデプロイするためのパイプライン定義ファイル
stages:
- deploy
variables: # ここはGitLab CIの変数で設定推奨
REGISTRY_HOST: "your-name" #今回はDockerHubを用いるためDockerHubのユーザ名
DEFAULT_CI_IMAGE: "your-name/kubernetes:latest" #後ほどDockerfileからビルドするイメージを使います。
GCP_KMS_KEY: projects/${GCP_PROJECT_ID}/locations/global/keyRings/secret_keyring/cryptoKeys/secret_key
before_script:
# サービスアカウントキーで認証
- echo "$GCP_KEY" > ${CI_PROJECT_DIR}/gcp-key.json
- export GOOGLE_APPLICATION_CREDENTIALS="${CI_PROJECT_DIR}/gcp-key.json"
- gcloud auth activate-service-account --key-file=${CI_PROJECT_DIR}/gcp-key.json
- gcloud config set project ${GCP_PROJECT_ID}
# GKE クラスタ認証情報取得
- gcloud container clusters get-credentials ${CLUSTER_NAME} --zone ${CLUSTER_ZONE}
- export KUBECONFIG=~/.kube/config
# SOPSインストール
- curl -LO https://github.com/getsops/sops/releases/download/v3.10.2/sops-v3.10.2.linux.amd64
- chmod +x sops-v3.10.2.linux.amd64
- mv sops-v3.10.2.linux.amd64 ./sops
- ./sops -d k8s/postgres-secret.enc.yaml > k8s/postgres-secret.yaml
# 復号されたファイルは .gitignore 対象にすることで公開リポジトリへの漏洩を防止
deploy:
stage: deploy
script:
#暗号化マニフェストを除外してデプロイ
- find k8s -type f ! -name "*.enc.yaml" -exec kubectl apply -f {} \;
- kubectl get all
default:
image: $DEFAULT_CI_IMAGE
timeout: 90m
以下のページでSOPSの最新版を確認してください。
https://github.com/getsops/sops (SOPS公式ページ)
今回は.gitlab-ci.ymlの記述方法や文法は省略します。以下のページを参考にしてください。
https://gitlab-docs.creationline.com/ee/ci/quick_start/ (公式GitLab日本版)
https://archives.docs.gitlab.com/16.0/ee/ci/yaml/gitlab_ci_yaml.html (公式GitLab)
全体の目的
-
SOPS で暗号化された Secret を復号
-
GCP 認証と GKE クラスタ接続を行い
-
kubectl apply で K8s マニフェストを GKE にデプロイする
🧱 stages
stages:
- deploy
-
ジョブの実行ステージを定義(ここでは1つだけ: deploy)
-
必要があれば build, test, lint, release なども追加可能
⚙️ variables
variables: # ここはGitLab CIの変数で設定推奨
REGISTRY_HOST: "your-name" #今回はDockerHubを用いるためDockerHubのユーザ名
DEFAULT_CI_IMAGE: "your-name/kubernetes:latest" #後ほどDockerfileからビルドするイメージを使います。
GCP_KMS_KEY: projects/${GCP_PROJECT_ID}/locations/global/keyRings/secret_key/cryptoKeys/secret_key
-
DEFAULT_CI_IMAGE は CI 実行用の Docker イメージ(例: kubectl, gcloud,が入った独自イメージ)
-
CLUSTER_NAME, CLUSTER_ZONE などは GitLab の CI/CD variables に登録推奨(セキュリティ・再利用性向上)
-
GCP_KMS_KEY は SOPS による暗号化に使用する GCP KMS キーのパス(復号のため必要)
🛠 before_script
GitLab CI での事前処理。認証・準備がメイン
- echo "$GCP_KEY" > ${CI_PROJECT_DIR}/gcp-key.json
- GitLab に登録した CI/CD 変数 GCP_KEY(GCPサービスアカウントのJSONキー)をファイルに保存
- gcloud auth activate-service-account --key-file=${CI_PROJECT_DIR}/gcp-key.json
- gcloud config set project ${GCP_PROJECT_ID}
# GKE クラスタ認証情報取得
- gcloud container clusters get-credentials ${CLUSTER_NAME} --zone ${CLUSTER_ZONE}
- GCP認証 & GKEクラスタの kubeconfig を取得(kubectl を使う準備)
- sops -d k8s/postgres-secret.enc.yaml > k8s/postgres-secret.yaml
- Gitに含めた暗号化ファイルを復号し、Kubernetesが読める形式に変換(ポイント:k8s/postgres-secret.yaml は .gitignore 済)
🚀 deploy ジョブ
deploy:
stage: deploy
script:
- find k8s -type f ! -name "*.enc.yaml" -exec kubectl apply -f {} \;
- kubectl get all
-
デプロイ実行本体
-
kubectl apply でマニフェストを GKE に反映
-
kubectl get all で状態確認(Pod や Service の展開確認)
defalutセクション
default:
image: $DEFAULT_CI_IMAGE
timeout: 90m
-
使用する Docker イメージとタイムアウト時間の設定
-
イメージはvariablesセクションで定義したイメージを使用
🚀 ここまでで、セキュアなKubernetesマニフェストとCI/CDパイプラインの基礎準備が整いました。次章では、GitLabにおけるCI変数の設定とパイプラインの実行手順に進みます。
3.2 GitLabプロジェクトの作成とCI/CD変数の定義
もしGitLabのアカウント登録をしていない方は以下のページを参考に作成できます。
https://gitlab.com/users/sign_up (GitLabアカウント登録サイト)
https://mashimashi.net/skill/git/515/ (わかりやすいGitLabアカウント登録手順サイト)
まずGitLabでProjectを作成します。
Project名はお好きな名前を設定してください。
Projectが作成できましたら左側の設定から[Setting] > [CI/CD]に移り変数の定義をします。
📌 登録するCI/CD変数一覧(GitLabの [Settings] > [CI/CD] > Variables で設定)
Key | Type | 説明 |
---|---|---|
CLUSTER_NAME |
Variable | GKEクラスタの名前 |
CLUSTER_ZONE |
Variable | GKEクラスタのゾーン(例: asia-northeast1-a) |
GCP_PROJECT_ID |
Variable | GCPのプロジェクトID(ID欄に表示される文字列) |
GCP_KEY |
Variable | サービスアカウントのJSONキー内容(ファイル中身をコピー) |
KUBECONFIG_DATA |
File |
~/.kube/config の中身(クラスタ認証情報) |
それぞれ変数の内容を解説していきます。
GKEでkubernetesクラスターを作成していなければ作成してください。作成の仕方は以下のコマンドで作成できます。これには5分ほどかかる場合があります。
詳しくは以下のページを参考にしてください
https://qiita.com/Elie1729/items/ad527d8617ac95bab751 (GKEクラスタ構築例)
$ gcloud container clusters create <cluster-name> --zone=asia-northeast1-a
# クラスタ名は任意でOKです(例: `gitlab-gke`)。必要に応じて `--num-nodes` を指定することで初期ノード数も調整できます。
🔐 GitLab に ~/.kube/config を登録
まず~/.kube/configを生成。クラスタの認証情報を取得。.kube/以下にconfigファイルが存在するか確認
$ gcloud container clusters get-credentials gitlab-gke --zone asia-northeast1-a
$ ls .kube
✅ サービスアカウントとGCP認証の準備
サービスアカウント作成(IAM)
$ gcloud iam service-accounts create gitlab-deployer --display-name "GitLab Deployer"
Kubernetesアクセス権限付与。
YOUR_PROJECT_IDはGCPで表示されているプロジェクトIDに置き換えてください。
番号: ・・・・ ID: ・・・・ のIDの方です。
$ gcloud projects add-iam-policy-binding YOUR_PROJECT_ID --member="serviceAccount:gitlab-deployer@YOUR_PROJECT_ID.iam.gserviceaccount.com" --role="roles/container.admin"
JSONキーを生成して保存、key.jsonが生成されたかを確認
$ gcloud iam service-accounts keys create key.json --iam-account=gitlab-deployer@YOUR_PROJECT_ID.iam.gserviceaccount.com
$ ls
CLUSTER_NAME: GKEのクラスター名
先ほど作成したGKEクラスタ名を定義します。例えばgke-clusterと名前を設定していたらKEYはCLUSTER_NAMEでVALUEはgke-clusterと入力して保存します。
CLUSTER_ZONE: GKEクラスターのゾーン
先ほどの例ではasia-northeast1-aで設定したのでKEYをCLUSTER_ZONE、VALUEをasia-northeast1-aと入力して保存します。
GCP_KEY: GCPにGitLabからアクセスするための情報
JSONキーをGitLab CI/CDのvariableにGCP_KEYとして登録
先ほどのkey.jsonコピー( { から } までをすべてコピー)
$ cat key.json
{
・・・・・・・・
・・・・・・・・
}
KEY:GCP_KEY
Value:コピーしたJSON内容
GCP_PROJECT_ID: GCPのプロジェクトID
これはGCPでプロジェクトを作成した際に付与される一意のIDです。番号: ・・・・ ID: ・・・・ のIDの方です。
KEY: GCP_PROJECT_ID
VALUE: ID
KUBECONDIG_DATA: クラスタの認証情報
~/.kube/configはGCPコンソールからcatなどで表示し、コピーしてCI/CDのVariablesにペーストしてください。ファイル名ではなく中身であることに注意。
⚠ 注意:これは機密情報のため、外部に公開しないこと!セキュリティ上、絶対に公開リポジトリやIssue等に貼らないよう注意しましょう。
ここでこれまでは変数のTypeはVariableでしたがこの変数のみFileとして扱います。
KEY:KUBECONFIG_DATA
Type:File
Value:~/.kube/config の内容
🧩 これで、GitLab側で必要なすべてのCI/CD変数が定義できました。
3.3 GCP KMSキーの作成とSOPSによるSecret暗号化
今回SOPSで暗号化するために用いる暗号化キーはGCP KMS(Google Cloud KMS)キーを利用します。
以下のコマンドでキーを作成します。
# KMSキーリングの作成(グローバルに作成)
$ gcloud kms keyrings create secret_keyring --location global
# KMS暗号化キーの作成
$ gcloud kms keys create secret_key --keyring secret_keyring --location global --purpose encryption
このキーは、SOPSでKubernetes Secretを安全に暗号化・復号化するために使用します。
キーが作成できたら実際にSOPSでsecretマニフェストを暗号化します。
暗号化するファイル: postgres-secret.yaml
YOUR_PROJECT_IDはGCPのプロジェクトIDに変更してください。
また、sops は gcloud CLI の認証情報を使うので認証を済ませておく
# 認証情報を取得(SOPSはgcloudの認証情報を利用)
$ gcloud auth application-default login
# postgres-secret.yaml を GCP KMS で暗号化
$ sops -e --gcp-kms projects/YOUR_PROJECT_ID/locations/global/keyRings/secret_keyring/cryptoKeys/secret_key postgres-secret.yaml > postgres-secret.enc.yaml
$ cat postgres-secret.enc.yaml
暗号化ができたら次にGitLabがGCP KMSキーを復号化用で参照できるように権限を付与します。
🔐 GitLabデプロイ用サービスアカウントにKMS復号権限を付与
- GCPコンソール左メニュー → [IAMと管理] → [サービスアカウント]
-
gitlab-deployer
を選択 - 「権限を編集」をクリック
- ロールを追加:Cloud KMS 暗号鍵復号者(roles/cloudkms.cryptoKeyDecrypter)
- 保存
これでGitLab CIパイプラインでpostgres-secret.enc.yamlを復号化してデプロイすることが可能となりました。
3.4 DockerfileでイメージをビルドとDockerHubへのプッシュ
次にnodejs-app/ディレクトリにあるDockerfileでイメージをビルドしてDockerHubにプッシュします。
DockerHubのアカウントを登録してない方は以下のページを参考に作成できます
https://hub.docker.com/ (DockerHub公式ページ)
https://docs.docker.jp/docker-hub/accounts.html (DockerHubアカウント作成手順)
# nodejs-app ディレクトリに移動
$ cd nodejs-app
# Dockerイメージをビルド
$ docker build -t <your-username>/todo-app:latest .
# イメージがビルドできたかを確認
$ docker images
# DockerHub にログイン
$ docker login
# イメージをDockerHubにプッシュ
$ docker push <your-username>/todo-app:latest
-
<your-dockerhub-username>
にはご自身のDockerHubアカウント名を入力してください。 - ビルド・プッシュが成功すれば、後ほどDeploymentのimageでこのイメージを参照できます。
イメージがビルドできたかを確認するにはDockerHubの公式ページでログインすることで確認できます。
https://hub.docker.com/ (DockerHub)
後ほどkubectl apply -f k8s/でnode.jsをデプロイしますがこのイメージをpullして使います。
🛠 もしビルドやプッシュが難しい場合:
以下の私のパブリックイメージを代用できます。
イメージ名: elie3141/todo-app:latest
🔧 これで、アプリケーションをDockerイメージとしてビルド・登録する準備が整いました。
4. GitLabからGKEへの自動デプロイ
手順は長かったですがGitLabに先ほどのファイルやディレクトリをpushするとGitLabが.gitlab-ci.ymlを読み込んでパイプラインが動きます。
🔐 SSHキーの生成とGitLabへの登録(初回のみ)
GitLabにリポジトリをpushするためにはSSHキーが必要です。
以下のコマンドでキーを作成します:
$ ssh-keygen -t rsa
実行後現在のディレクトリに.sshというディレクトリが作成されます。
このディレクトリ内のid_rsa.pub(公開鍵)をGitLabに登録します。GitLabプロジェクト作成後、上部にSSHキーを追加してくださいというメッセージが表示されていると思うのでそこから今回のid_rsa.pubを登録してください。id_rsa.pubともう一つid_rsaが.ssh/にありますが、これは秘密鍵なので外部に公開しないようにしましょう。
詳しくは以下のページは詳しくSSHキー作成手順を解説しています。必要に応じて参考にしてください。
https://qiita.com/to3izo/items/9b5b80430e43cd3c4e3c
次に作成したファイルをコミットしてGitLabにプッシュしましょう。
ディレクトリは
k8s_secure_secret_resource_with_sopsに移動して以下のコマンドを実行
# 作業ディレクトリに移動
$ cd k8s_secure_secret_resource_with_sops
# Gitにファイルを登録してコミット
$ git add .
$ git commit -m "add all files"
# GitLabにpush(mainブランチ)
$ git push -u origin main
成功したらGitLabに移動してプロジェクトを見るとファイルがportgres-secret.yaml以外追加されていると思います。そして自動でgitlab-ci.ymlを読み込んでパイプラインが発していると思います。
[CI/CD] > [Pipelines] タブからジョブの進行状況を確認できます。
- 各ジョブが
passed
と表示されたら成功です。 - 実行には1〜5分ほどかかることがあります。
GCPでkubectl get allで確認できます。
これでGitLab×GKEのパイプライン構築が完了しました。
5. ToDoアプリの動作確認(登録&取得)
先ほどのkubectl get allですべてのリソースが正常にデプロイが確認出来たらserviceのEXTERNAL-IPのIPアドレスをメモしてください。これは外部からToDoアプリにアクセスする際のIPアドレスです。そのIPアドレスを以下ではxx.xx.xx.xxとします。
$ kubectl get svc
# 出力例
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT AGE
nodejs-service LoadBalancer 10.0.0.42 xx.xx.xx.xx 80:32462/TCP 5m
以下のコマンドでToDoを1件登録してみましょう。
# データを1件登録
$ curl -X POST http://xx.xx.xx.xx/todos -H "Content-Type: application/json" -d '{"title": "Kubernetes学習"}'
次にデータを取り出します。
# データを取得
$ curl http://34.146.172.56/todos
[
{
"id": 1,
"title": "Kubernetes学習",
"done": false,
"created_at": "2025-06-27T00:00:00.000Z"
}
]
このようなJSONレスポンスが返ってくれば、ToDoアプリは正常に動作しています。
Node.jsアプリ、PostgreSQL、Secret の連携が正しく構成されている証拠です。
非常に長かったと思いますが、セキュアなsecretリソースの運用方法を実践的なアプリケーションを例に学んだことで技術力が確実に上がったと思います
🎉 お疲れさまでした
ここまでで、GitLab CI/CD × GKE × SOPS によるセキュアなアプリケーション運用フローを一通り終えました。
ToDoアプリという実用例を通して、以下の技術が確実に身についたはずです:
- GCP KMS と SOPS を用いた Secret の暗号/復号化・管理
- GitLab CI/CD による自動ビルド・デプロイ
- Kubernetes 上でのアプリケーション構築・公開
この経験は、今後のクラウド開発・DevOps案件で大きな強みになると思います🔥
✅ 6. まとめと今後の展望
お疲れ様でした!
このプロジェクトでは、「Node.js + PostgreSQL」をバックエンドに採用したToDoアプリAPIを構築し、Kubernetes + GitLab CI/CD + SOPS によってセキュアかつ自動化されたデプロイを実現しました。
現時点で、以下の要素が動作しています:
-
RESTful APIによるToDoデータの操作(取得・追加・削除)
-
PostgreSQLとの永続的なデータ連携
-
Kubernetes上でのアプリケーション実行
-
SOPSとGCP KMSによるSecret管理の自動化
✅ この構成の実用性
このようなNode.js + PostgreSQLの構成は、WebアプリのAPIサーバーの基本形であり、以下のようなサービスに応用可能です:
-
タスク管理(今回のようなToDo)
-
ユーザー登録・ログインAPI
-
ショッピングカートの中身管理
-
簡易CMSバックエンド
🚀 今後の展望
このToDoアプリAPIは、以下のように拡張・発展させることで実用的なWebアプリケーションへと成長させることができます。
- フロントエンドの追加
-
React や Vue.js などのモダンなJavaScriptフレームワークを導入し、
-
直感的で操作性の高いGUI を持ったフロントエンドを構築
- SPA(Single Page Application)の構築
-
クライアント側でルーティングを制御することで、ページ遷移のないスムーズなUIを実現
-
axios や fetch を用いて、Node.js APIと連携し、ToDoの状態をリアルタイムに反映
- 認証・認可の導入
-
JWTを用いたユーザー認証機能
-
ユーザーごとのToDo管理など、実サービスに近い要件への対応
- 本番環境への公開
-
GCPのCloud Load BalancingやCloud SQLとの連携
-
Let's Encrypt + Ingress + TLS によるHTTPS対応
-
ドメイン設定を含めた本番運用の準備
💡最後に
本記事では、「Kubernetesを活用した実践的なWebアプリ開発・運用の第一歩」を踏み出すことができました。
ここからさらに、DevOpsスキルの強化やクラウドインフラ設計の理解へとつなげていくことで、より実務的なエンジニアリング力が身についていきます。
今後のステップアップの一助となれば幸いです。お疲れ様でした!🚀