0. この記事で作るもの
IBM Cloud Kubernetes Service(以下 IKS)上に、
- Redis(Celery の broker / result backend)
- Celery worker
- Celery beat(定期実行)
- Web アプリ(例: Django / FastAPI 等)
をデプロイし、「IKS 固有の落とし穴や運用ポイント」まで含めて動かす。
1. 全体アーキテクチャ
最小構成は次の通り。
[Web Deployment] --(enqueue task)--> [Redis Service] <--(consume)-- [Celery Worker Deployment]
^
|
[Celery Beat Deployment]
- Redis は StatefulSet で永続化(PVC)
- worker / beat / web は同一イメージの “コマンド違い” で分けるのが一般的
- スケール対象は worker(beat は基本 1 replica)
2. 前提・注意(IKS ならでは)
2.1 VPC クラスタの “Secure by Default” に注意
IKS の VPC クラスタを Kubernetes 1.30 以降で新規作成した場合、外向き通信がデフォルトで強く制限され、Docker Hub など パブリックレジストリからの pull がブロックされることがある。つまり、Redis やアプリのイメージを Docker Hub 参照のままにしていると ImagePullBackOff になる可能性が高い。(IBM Cloud)
対策はどれか。
- 重要イメージは IBM Cloud Container Registry(ICR)へミラー/自前 push
- もしくは Secure by Default を解除/許可ルール追加(運用ポリシー次第)
この記事では「ICR へ置く」前提で進める。
2.2 Helm で入れるのが前提
IKS は通常の Kubernetes なので Helm v3 で問題ない。IBM 公式も Helm を前提にした手順が多い。(IBM Cloud)
3. クラスタ準備
3.1 CLI
ローカルに以下を用意。
- ibmcloud CLI
- container-service plugin
- kubectl
- helm v3
(インストールは公式手順に従う。)
3.2 クラスタへ接続
ibmcloud login
ibmcloud ks cluster config --cluster <CLUSTER_NAME_OR_ID>
kubectl config current-context
3.3 Namespace 作成
kubectl create namespace celery-demo
3.4 ICR の imagePullSecret を namespace にコピー(重要)
IKS では default namespace に all-icr-io という pull secret が自動作成されるが、別 namespace では使えない。そのままだと private ICR イメージが pull できない。(IBM Cloud)
kubectl get secret all-icr-io -n default -o yaml \
| sed 's/namespace: default/namespace: celery-demo/' \
| kubectl apply -n celery-demo -f -
kubectl patch serviceaccount default -n celery-demo \
-p '{"imagePullSecrets":[{"name":"all-icr-io"}]}'
これで celery-demo 内の Pod は暗黙に ICR を pull できる。
4. Redis を Helm でデプロイ
ここではメンテされている Bitnami の redis chart を使う。
理由:
- 永続化や Sentinel/replica 設定が values.yaml で素直にできる
- Secret の払い出し仕様が安定している
4.1 StorageClass を確認して決める
IKS には複数の StorageClass が入っている。名前はクラスタ種別(VPC/Classic)やアドオンで変わるので、固定で書かない。
kubectl get storageclass
block系(RWO)を Redis の永続化に使うことが多い。IKS の block/file ストレージは PVC 経由で動的作成できる。(IBM Cloud)
4.2 values.yaml
redis-values.yaml を作る。
auth:
enabled: true
# Sentinel 構成やアプリ側の接続文字列を安定させるため、ランダム生成にせず固定で入れる
password: "CHANGE_ME_STRONG_PASSWORD"
master:
persistence:
enabled: true
size: 10Gi
storageClass: "<YOUR_STORAGECLASS>"
resources:
requests:
cpu: "100m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "1Gi"
replica:
replicaCount: 1
persistence:
enabled: true
size: 10Gi
storageClass: "<YOUR_STORAGECLASS>"
補足:
- Bitnami chart はパスワードを Secret
release-name-redisの keyredis-passwordに保存する。(GitHub) - Sentinel を使う場合、auth.password を明示しないと挙動が不安定になるケースが報告されているので固定推奨。(GitHub)
4.3 インストール
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm install redis bitnami/redis \
-n celery-demo \
-f redis-values.yaml
確認。
kubectl get pods -n celery-demo -l app.kubernetes.io/name=redis
kubectl get svc -n celery-demo
5. アプリ(Celery)イメージを ICR に push
5.1 ICR namespace 作成
ibmcloud cr namespace-add <YOUR_NAMESPACE>
ibmcloud cr login
5.2 build & push
# 例: myapp:latest を作り、ICRへpush
docker build -t myapp:latest .
docker tag myapp:latest jp.icr.io/<YOUR_NAMESPACE>/myapp:latest
docker push jp.icr.io/<YOUR_NAMESPACE>/myapp:latest
6. Celery / Web をデプロイ
以降は Kubernetes YAML を使う。
Redis の接続情報は Secret から拾うようにする。
6.1 Redis パスワード Secret を読む
Bitnami redis の Secret 名は redis-redis(release 名 + redis)なので、確認してから参照する。
kubectl get secret -n celery-demo | grep redis
kubectl get secret redis-redis -n celery-demo -o jsonpath="{.data.redis-password}" | base64 --decode
6.2 共通 Secret / ConfigMap
celery-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: celery-secrets
namespace: celery-demo
type: Opaque
stringData:
REDIS_HOST: "redis-master.celery-demo.svc.cluster.local"
REDIS_PORT: "6379"
# redis-values.yaml と同じ値にする
REDIS_PASSWORD: "CHANGE_ME_STRONG_PASSWORD"
celery-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: celery-config
namespace: celery-demo
data:
CELERY_BROKER_DB: "0"
CELERY_RESULT_DB: "1"
CELERY_TASK_TIME_LIMIT: "300"
CELERY_WORKER_CONCURRENCY: "4"
6.3 Web Deployment
web.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: celery-demo
spec:
replicas: 2
selector:
matchLabels: { app: web }
template:
metadata:
labels: { app: web }
spec:
containers:
- name: web
image: jp.icr.io/<YOUR_NAMESPACE>/myapp:latest
imagePullPolicy: IfNotPresent
command: ["bash","-c"]
args:
# 例: Django
- "python manage.py migrate && gunicorn config.wsgi:application -b 0.0.0.0:8000"
envFrom:
- secretRef: { name: celery-secrets }
- configMapRef: { name: celery-config }
env:
- name: CELERY_BROKER_URL
value: "redis://:$(REDIS_PASSWORD)@$(REDIS_HOST):$(REDIS_PORT)/$(CELERY_BROKER_DB)"
- name: CELERY_RESULT_BACKEND
value: "redis://:$(REDIS_PASSWORD)@$(REDIS_HOST):$(REDIS_PORT)/$(CELERY_RESULT_DB)"
ports:
- containerPort: 8000
resources:
requests:
cpu: "100m"
memory: "256Mi"
limits:
cpu: "1"
memory: "1Gi"
web-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: web
namespace: celery-demo
spec:
type: ClusterIP
selector:
app: web
ports:
- port: 80
targetPort: 8000
外部公開は IKS の Ingress(ALB)を使うのが一般的。ここは環境ごとに差が出るため割愛。
6.4 Celery Worker Deployment
worker.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: celery-worker
namespace: celery-demo
spec:
replicas: 2
selector:
matchLabels: { app: celery-worker }
template:
metadata:
labels: { app: celery-worker }
spec:
terminationGracePeriodSeconds: 60
containers:
- name: worker
image: jp.icr.io/<YOUR_NAMESPACE>/myapp:latest
command: ["bash","-c"]
args:
- >
celery -A config.celery_app worker
--loglevel=INFO
--concurrency=$(CELERY_WORKER_CONCURRENCY)
--prefetch-multiplier=1
envFrom:
- secretRef: { name: celery-secrets }
- configMapRef: { name: celery-config }
env:
- name: CELERY_BROKER_URL
value: "redis://:$(REDIS_PASSWORD)@$(REDIS_HOST):$(REDIS_PORT)/$(CELERY_BROKER_DB)"
- name: CELERY_RESULT_BACKEND
value: "redis://:$(REDIS_PASSWORD)@$(REDIS_HOST):$(REDIS_PORT)/$(CELERY_RESULT_DB)"
resources:
requests:
cpu: "200m"
memory: "512Mi"
limits:
cpu: "2"
memory: "2Gi"
ポイント:
-
terminationGracePeriodSecondsを置き、長い task を途中 kill しないようにする -
prefetch-multiplier=1は偏りを減らす実運用定番
6.5 Celery Beat Deployment
beat.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: celery-beat
namespace: celery-demo
spec:
replicas: 1
selector:
matchLabels: { app: celery-beat }
template:
metadata:
labels: { app: celery-beat }
spec:
containers:
- name: beat
image: jp.icr.io/<YOUR_NAMESPACE>/myapp:latest
command: ["bash","-c"]
args:
- "celery -A config.celery_app beat --loglevel=INFO"
envFrom:
- secretRef: { name: celery-secrets }
- configMapRef: { name: celery-config }
env:
- name: CELERY_BROKER_URL
value: "redis://:$(REDIS_PASSWORD)@$(REDIS_HOST):$(REDIS_PORT)/$(CELERY_BROKER_DB)"
beat は多重起動すると二重スケジュールになるので、基本は 1 replica 固定。
6.6 まとめて apply
kubectl apply -f celery-secret.yaml
kubectl apply -f celery-config.yaml
kubectl apply -f web.yaml -f web-svc.yaml
kubectl apply -f worker.yaml
kubectl apply -f beat.yaml
動作確認:
kubectl get pods -n celery-demo
kubectl logs -n celery-demo deploy/celery-worker
kubectl logs -n celery-demo deploy/celery-beat
7. 運用ポイント(深いところ)
7.1 Redis の永続化とバックアップ
- PVC を消すと Redis データは消える
- StorageClass の reclaimPolicy が
Deleteだと PVC 削除で実体も削除され、課金も止まるがデータも消える。(IBM Cloud) - 本番では Snapshot / 外部バックアップを検討
7.2 Celery のスケーリング
基本は worker を水平スケール。
HPA で CPU スケールでも良いが、「キューの長さでスケール」したいなら KEDA の Redis scaler が使いやすい。
- IKS は通常の Kubernetes 拡張が入れられるので KEDA は問題なく動くはず
- ただし VPC Secure by Default を有効にしている場合、KEDA イメージ pull も ICR 化が必要になることがある(public pull が遮断されるため)。(IBM Cloud)
7.3 Graceful shutdown
worker の RollingUpdate 時に task が途中で落ちるのを避けたい場合:
-
terminationGracePeriodSecondsを長めに - task 側は
acks_late=True+ 冪等性 - SIGTERM の扱いは Celery 5 系は比較的素直だが、無限長 task は別設計が必要
7.4 Redis を HA にするか
- 単一 Redis はシンプルだが SPOF
- Sentinel / Cluster は値ファイルで構築可能。ただし運用コストも増えるので、要件で割り切る
8. 監視(Flower の例)
Celery の状態監視に Flower を置く。
ここでも ICR イメージ前提。
flower.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: flower
namespace: celery-demo
spec:
replicas: 1
selector:
matchLabels: { app: flower }
template:
metadata:
labels: { app: flower }
spec:
containers:
- name: flower
image: jp.icr.io/<YOUR_NAMESPACE>/myapp:latest
command: ["bash","-c"]
args:
- >
celery -A config.celery_app flower
--port=5555
envFrom:
- secretRef: { name: celery-secrets }
env:
- name: CELERY_BROKER_URL
value: "redis://:$(REDIS_PASSWORD)@$(REDIS_HOST):$(REDIS_PORT)/0"
ports:
- containerPort: 5555
---
apiVersion: v1
kind: Service
metadata:
name: flower
namespace: celery-demo
spec:
selector:
app: flower
ports:
- port: 80
targetPort: 5555
必要なら Ingress を追加して UI を外から見られるようにする。
9. よくある詰まりどころ
9.1 ImagePullBackOff
原因はだいたい以下。
-
namespace に all-icr-io をコピーしてない
→ 3.4 を再確認。(IBM Cloud) -
VPC Secure by Default で public registry が遮断
→ ICR に置く、または outbound 設定変更。(IBM Cloud)
9.2 Redis に接続できない
- Service 名を間違えている(
redis-master.<ns>.svcが正) - パスワード不一致
-
auth.password未指定でランダム生成され、アプリ側が追従できていない
→ values.yaml で固定推奨。(GitHub)
9.3 worker が task を拾わない
- queue 名の不一致
- worker 側の concurrency が過剰で OOM
- beat が 2 つ以上動いて二重投入している(replicas=1 を守る)
10. まとめ
IKS 上で Celery + Redis を動かすのは Kubernetes 標準のやり方でほぼ完結するが、IKS 特有のポイントは以下が大きい。
- VPC クラスタの Secure by Default により public registry pull が止まる可能性
→ コアとなるイメージは ICR へ置く - ICR pull secret
all-icr-ioは default namespace 限定
→ 使う namespace へコピー&ServiceAccountへ紐付け - Redis のパスワードは固定し、Secret 経由で Celery 側へ渡す
ここまで整えておけば、worker のスケールや HA 化、監視追加に素直に進める。
by 花村勇輝(Hanamura Yuki)