0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

IBM Cloud Kubernetes Service 上で Celery + Redis を構築する

Last updated at Posted at 2025-11-22

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 の key redis-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

原因はだいたい以下。

  1. namespace に all-icr-io をコピーしてない
    → 3.4 を再確認。(IBM Cloud)

  2. 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)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?