Claude CodeでPRプレビュー環境を自動構築する:K8s Namespace・自動URLコメント・クリーンアップ
PRごとに独立したプレビュー環境を自動構築すると、レビュアーがコードを読むだけでなく実際に動作を確認できるようになります。Claude Codeに「GitHub ActionsとKubernetesを使ったPRプレビュー環境の仕組みを実装して」と依頼すると、このような構成が出てきます。本記事ではその実装を詳しく解説します。
アーキテクチャ概要
PR open/update
│
▼
GitHub Actions (preview-deploy job)
│
├─ Namespace: preview-pr-{number} 作成
├─ ResourceQuota 適用(コスト制限)
├─ Helm deploy --values preview-values.yaml
├─ Ingress URL: https://preview-pr-{number}.example.com
└─ PR にコメント投稿(既存コメントは更新)
PR close/merge
│
▼
GitHub Actions (preview-cleanup job)
└─ Namespace ごと削除
GitHub Actionsワークフロー
.github/workflows/preview.yml に以下を配置します。
name: PR Preview Environment
on:
pull_request:
types: [opened, synchronize, reopened, closed]
jobs:
deploy-preview:
if: github.event.action != 'closed'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write # PRコメント投稿に必要
steps:
- uses: actions/checkout@v4
- name: Set preview namespace
run: echo "NAMESPACE=preview-pr-${{ github.event.number }}" >> $GITHUB_ENV
- name: Configure kubectl
uses: azure/k8s-set-context@v3
with:
method: kubeconfig
kubeconfig: ${{ secrets.KUBECONFIG }}
- name: Create namespace
run: |
kubectl create namespace $NAMESPACE --dry-run=client -o yaml | kubectl apply -f -
- name: Apply ResourceQuota
run: |
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ResourceQuota
metadata:
name: preview-quota
namespace: $NAMESPACE
spec:
hard:
requests.cpu: "500m"
requests.memory: "512Mi"
limits.cpu: "1"
limits.memory: "1Gi"
count/pods: "10"
EOF
- name: Deploy with Helm
run: |
helm upgrade --install my-app ./charts/my-app \
--namespace $NAMESPACE \
--values charts/my-app/values-preview.yaml \
--set image.tag=${{ github.sha }} \
--set ingress.host=pr-${{ github.event.number }}.preview.example.com \
--wait --timeout 5m
- name: Comment PR with preview URL
uses: actions/github-script@v7
with:
script: |
const previewUrl = `https://pr-${{ github.event.number }}.preview.example.com`;
const body = [
'## プレビュー環境',
'',
`| 項目 | 値 |`,
`|------|-----|`,
`| URL | ${previewUrl} |`,
`| Namespace | preview-pr-${{ github.event.number }} |`,
`| Commit | ${{ github.sha }} |`,
`| 更新日時 | ${new Date().toISOString()} |`,
'',
'> このコメントはコミットごとに自動更新されます。',
].join('\n');
// 既存コメントを検索して更新(重複投稿防止)
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const existing = comments.find(
c => c.user.login === 'github-actions[bot]' && c.body.includes('## プレビュー環境')
);
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
}
cleanup-preview:
if: github.event.action == 'closed'
runs-on: ubuntu-latest
steps:
- name: Configure kubectl
uses: azure/k8s-set-context@v3
with:
method: kubeconfig
kubeconfig: ${{ secrets.KUBECONFIG }}
- name: Delete preview namespace
run: |
NAMESPACE=preview-pr-${{ github.event.number }}
kubectl delete namespace $NAMESPACE --ignore-not-found=true
echo "Namespace $NAMESPACE を削除しました"
Helm preview-values.yaml
本番との差分を最小限にしつつ、コストを抑えた設定にします。
# charts/my-app/values-preview.yaml
replicaCount: 1 # プレビューは1レプリカで十分
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
ingress:
enabled: true
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-staging # プレビューはstaging証明書
tls:
- secretName: preview-tls
hosts:
- "*.preview.example.com"
# DBはプレビュー用の軽量SQLiteを使用
database:
type: sqlite
path: /tmp/preview.db
# 外部APIはモック
featureFlags:
useMockExternalAPIs: true
Ingressコントローラーのワイルドカード設定
すべてのPRで *.preview.example.com を使えるよう、ワイルドカードDNSと証明書を設定します。
# cert-managerでワイルドカード証明書(Let's Encrypt)
kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: preview-wildcard
namespace: ingress-nginx
spec:
secretName: preview-wildcard-tls
dnsNames:
- "*.preview.example.com"
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
EOF
コスト制御のポイント
放置されたプレビュー環境がコストを食い続けないよう、追加で対策します。
# 古いプレビュー環境を定期削除するCronJob
apiVersion: batch/v1
kind: CronJob
metadata:
name: cleanup-stale-previews
namespace: kube-system
spec:
schedule: "0 2 * * *" # 毎日午前2時
jobTemplate:
spec:
template:
spec:
serviceAccountName: preview-cleaner
containers:
- name: cleaner
image: bitnami/kubectl:latest
command:
- /bin/sh
- -c
- |
# 7日以上前に作成されたpreview-pr-*ネームスペースを削除
kubectl get ns -o json | \
jq -r '.items[] | select(.metadata.name | startswith("preview-pr-")) |
select(.metadata.creationTimestamp | fromdateiso8601 < (now - 604800)) |
.metadata.name' | \
xargs -r kubectl delete ns
restartPolicy: OnFailure
Claude Codeへの指示パターン
以下の要件でPRプレビュー環境を自動構築するGitHub Actionsを実装してください。
要件:
- PR open/update時: preview-pr-{PR番号} のK8s Namespaceを作成
- ResourceQuotaでCPU/メモリを制限(requests.cpu: 500m, memory: 512Mi)
- Helmでデプロイ、preview-values.yamlを使用
- PRにプレビューURLをコメント(既存コメントは更新、重複させない)
- PR close/merge時: Namespaceごと削除
まとめ
-
PR番号でNamespaceを分離:
preview-pr-{number}にすることで環境が衝突せず、クリーンアップも簡単 - ResourceQuotaでコスト上限を設ける:プレビュー環境が意図せずリソースを食い潰さないよう必須設定
-
コメント更新で重複投稿を防ぐ:
github-actions[bot]の既存コメントを検索してupdateCommentを使う -
PR closeと同時にNamespace削除:
kubectl delete namespaceはリソースをまとめて削除できるため、クリーンアップが一行で完結する
Code Review Pack ¥980 — PRレビューをClaude Codeで自動化する5プロンプトセット。セキュリティ・パフォーマンス・設計の観点から自動レビューコメントを生成します。
prompt-works.jp で販売中。
Top comments (0)