1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Docker/Kubernetesデプロイ前の不安を減らす、Promote Strategyという検証ワークフロー

1
Last updated at Posted at 2026-06-22

はじめに

筆者が開発しているコンテナランタイム「Raind」、Docker-like/Kubernetes-likeなコンテナ/Pod起動を一つのランタイムで扱えるというのがコアになっているのですが、
今回は新たに実装した機能 「Promote Strategy」 についてご紹介しようと思います。

GitHub Repoはこちら: https://github.com/shizuku198411/Raind
"Star"をいただけると大変励みになります!

Promote Strategyとは?

Promote Strategyとは、簡単に言うと アプリケーションの動作テストを、Container/Compose/Podまで一つのワークフローとして行う 機能です。
まずはデモ画面を載せておきます。

このデモ画面では、以下のようなものを一つのワークフローとして自動で行っています

  1. アプリケーションをまずはコンテナとして起動できるかの確認
  2. コンテナとしての起動が確認できたら、Composeスタイルに昇格
  3. Composeスタイルでアプリケーション間連携やアプリケーション自体が正しく行われているかの確認
  4. Composeスタイルでの起動が確認できたら、Kubernetesスタイルに昇格
  5. Kubernetesスタイルでアプリケーション間連携やアプリケーション自体が正しく行われているかの確認

これを見た方は、以下のようなことを思うかもしれません。

1. Kubernetesでデプロイする前提なら、わざわざContainerから検証する必要ってなくない?

これはおっしゃる通りだと思います。
しかしながら、Kubernetesでのデプロイ、言い換えるとPodとしてアプリケーションを起動するというのは

  • そもそもコンテナとして起動できるアプリケーション(イメージ)であることが前提

であり、加えてKubernetesにはデプロイのための様々な機能(readiness probeとかjobとかingressとかCNIとか...)が含まれます。
慣れている人ならいいかもしれませんが、あまりKubernetesに慣れていないユーザがローカルで動作確認をしたい場合に
初めからKubernetesにデプロイして検証する、というのは非常にハードルが高いです。
デプロイができない、Podは起動したけどアプリが起動しないなどなど...何が原因かを把握するのも難しいですし、
そもそもアプリとして正しく起動する構成になっているかの確認も難しいです。

そのような場合、まずはコンテナとして起動するかを確認することで、少なくともアプリケーションの構成が問題かどうかの切り分けが可能になります。

2. こんなランタイム使わずに、Docker/Kubernetesを使うほうが確実じゃない?

これはド正論すぎるのでダメージがでかいです。
なんですが、Docker/Kubernetesを必ず使えるかというとそうではない場合もあったりします。
例えばDevContainer的な感じで開発環境が固定されていたり、そもそもグローバルにKubernetesなどを入れたくない場合などもあります。
※筆者は開発作業用ホストにはDocker/Kubernetesは入れておらず、別のホストに構築するようにしてます。開発作業ホストはいつ壊れてもいいように検証基盤系を入れたくないという思想です。

さらに、ステージングやSandbox的に用意されていたとしても、簡単には使えない状況もあります。
ローカルで構築したアプリケーションのイメージはどういうタグでビルドすればいいのか?Docker/Kubernetes両方で利用するためにはRegistryにPushしないといけないのか?など...
GitLabのようなCICD基盤があればここら辺はパイプラインを通じて一定のルールで運用できますが、
これも "簡単にテストをしたい" という状況においては少々簡単とは言えません。

そのようなところから、DockerでもKubernetesでもなく、ローカル環境でどちらの動作も検証することが可能なランタイムが便利になるわけです。

3. kind/minikubeを使えばよくない?

Kubernetesへのデプロイ検証という観点であればその通りです。
一方で、これらはKubernetesクラスタを構築する仕組みであり、Dockerのようなコンテナとして起動するような仕組みには重きを置いていません。
先ほども書いた通り、コンテナとして起動するかどうかの検証には引き続きDockerが必要になります。

そうなると、Docker/minikube/Kubernetesといった3つのランタイムを行き来する必要がでてきてしまい、
これはこれで環境が複雑になっていきます。

つまるところ...

筆者が目指しているのは、Docker/Kubernetesの完全互換でも、ただ単に両方の仕組みが動くだけのおもちゃのようなランタイムでもありません。
これら本番環境へデプロイする前に 最低限アプリケーションがContainer/Compose/Podとして起動できるかどうか の検証機能を提供することが最大の目的です。

ここまで読んでいただいた方は、「アプリケーションが動作すること」と「デプロイが正しくできること」は別々の意味であるということをご理解いただけたと思います。
Raindは前者である「アプリケーションが動作すること」にフォーカスしている、と伝われば幸いです。

レビュー可能な設定ファイルの生成

Promote Strategyは単に動作テストをしているだけではありません。同時にレビュー可能な設定ファイルの生成を行っています。
具体的には、

  1. Containerテストクリア後、Composeスタイルの設定ファイル(compose.yaml) を生成
  2. Composeスタイルテストクリア後、Kubernetesスタイルの設定ファイル(manifest.yaml) を生成

といった形で、テストを行う環境の昇格時に設定ファイルの生成を行っています。

ランタイム状態から設定ファイルを生成

文章だけ書くと単なる静的な変換のように見えますが、実際には ランタイム状態をもとに設定ファイルを生成 しています。
実際には以下のような仕組みです。

  • コンテナ/Composeが起動していることを確認し (コンテナ/Composeとしての起動保障)
  • 定義したアプリケーション動作テストがクリアしていることを確認し (アプリケーションの起動保障)
  • コンテナ内で実際に適用されている環境変数/エントリポイントを確認し
  • 観測されたコンテナ間通信やセキュリティポリシーを確認し
  • レビュー可能な設定ファイルを生成する

実際に生成されたCompose fileの例を以下に記載しておきます。

compose.yaml
# Generated by Raind Promote from container/mysql, container/wordpress.
# This compose.yaml is a reviewable draft, not production configuration.
# Review env, mounts, ports, and dependencies before sharing.

services:
  mysql:
    image: "mysql:8"
    command:
      - "docker-entrypoint.sh"
      - "mysqld"
    # TODO: secret candidate redacted from container env: MYSQL_PASSWORD
    # env example: MYSQL_PASSWORD=<redacted>
    # TODO: secret candidate redacted from container env: MYSQL_ROOT_PASSWORD
    # env example: MYSQL_ROOT_PASSWORD=<redacted>
    env:
      - "GOSU_VERSION=1.19"
      - "LANG=C.UTF-8"
      - "MYSQL_DATABASE=wordpress-db"
      - "MYSQL_MAJOR=8.4"
      - "MYSQL_SHELL_VERSION=8.4.10-1.el9"
      - "MYSQL_USER=wordpress-user"
      - "MYSQL_VERSION=8.4.10-1.el9"
    volumes:
      - "/mnt/mysql:/var/lib/mysql"
  wordpress:
    image: "wordpress:latest"
    command:
      - "docker-entrypoint.sh"
      - "apache2-foreground"
    # TODO: secret candidate redacted from container env: WORDPRESS_DB_PASSWORD
    # env example: WORDPRESS_DB_PASSWORD=<redacted>
    env:
      - "APACHE_CONFDIR=/etc/apache2"
      - "APACHE_ENVVARS=/etc/apache2/envvars"
      - "GPG_KEYS=1198C0117593497A5EC5C199286AF1F9897469DC C28D937575603EB4ABB725861C0779DC5C0A9DE4 AFD8691FDAEDF03BDF6E460563F15A9B715376CA"
      - "LANG=C.UTF-8"
      - "PHPIZE_DEPS=autoconf \t\tdpkg-dev \t\tfile \t\tg++ \t\tgcc \t\tlibc-dev \t\tmake \t\tpkg-config \t\tre2c"
      - "PHP_ASC_URL=https://www.php.net/distributions/php-8.3.31.tar.xz.asc"
      - "PHP_CFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64"
      - "PHP_CPPFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64"
      - "PHP_INI_DIR=/usr/local/etc/php"
      - "PHP_LDFLAGS=-Wl,-O1 -pie"
      - "PHP_SHA256=66410cee07f4b2baeb0843140bb2a2b52ef930b5cf9b3d6e6d158b33aae8fa37"
      - "PHP_URL=https://www.php.net/distributions/php-8.3.31.tar.xz"
      - "PHP_VERSION=8.3.31"
      - "WORDPRESS_DB_HOST=mysql"
      - "WORDPRESS_DB_NAME=wordpress-db"
      - "WORDPRESS_DB_USER=wordpress-user"
    ports:
      - "9850:80"
    depends_on:
      - "mysql"

通常手動で作成する場合はコマンドはイメージのデフォルトのものを使うため記載しないことが多いですし、環境変数もイメージがセットするものも見えているので通常は記載しません。

しかし、これらを出力することで、

  • エントリポイントのコマンドは正しいか?
  • 環境変数は正しいか?

といった確認ができます。これが レビュー可能な 設定ファイルと言っている由来です。
さらに、ランタイム状態から設定ファイルを作成することで、 少なくともアプリケーションとしては起動できる設定ファイルの土台 が生成されます。
もちろん生成されたファイルをそのまま使うことはできません(というより不足している設定もある)が、これを土台に不足している関連設定を定義していく、という土台としては十分に役に立ちます。

Promote Strategyを試してみる

ここからは実際にどのようにPromote Strategyを利用するかを紹介します。
ここでは Wordpress + MySQL を例にします。

Strategy定義ファイルの作成

まずはどのようなアプリケーションを起動し、どのような検証を行うかを定義します。

定義ファイル(raind-strategy.yaml)例
apiVersion: raind.io/v1alpha1
kind: PromoteStrategy

metadata:
  name: wordpress-stack

# 検証開始となるシードコンテナの定義
source:
  mode: create
  containers:
    - name: mysql
      image: mysql:8
      env:
        MYSQL_ROOT_PASSWORD: root-password
        MYSQL_DATABASE: wordpress-db
        MYSQL_USER: wordpress-user
        MYSQL_PASSWORD: wordpress-password
      volume:
        - /mnt/mysql:/var/lib/mysql

    - name: wordpress
      image: wordpress:latest
      env:
        WORDPRESS_DB_HOST: mysql
        WORDPRESS_DB_NAME: wordpress-db
        WORDPRESS_DB_USER: wordpress-user
        WORDPRESS_DB_PASSWORD: wordpress-password
      ports:
        - "9850:80"

  # コンテナ間で発生する通信の定義
  policies:
    - type: ew
      source: wordpress
      destination: mysql
      protocol: tcp
      destPort: 3306
      comment: allow wordpress database access

# ステージ毎の検証内容定義
stages:
  # Containerテスト
  container:
    checks:
      # Runtimeステータス検証、コンテナとしてそもそも起動することを確認
      runtime:
        - name: mysql-running
          type: containerStatus
          target: mysql
          expect:
            state: running
          timeout: 60s
          interval: 2s

        - name: wordpress-running
          type: containerStatus
          target: wordpress
          expect:
            state: running
          timeout: 60s
          interval: 2s

  # Bottle(Compose)テスト
  bottle:
    checks:
      # Runtimeステータス検証、Compose的に起動することを確認
      runtime:
        - name: bottle-running
          type: bottleStatus
          target: wordpress-stack
          timeout: 60s
          interval: 2s

      # アプリケーション検証、httpリクエストが成功することを確認
      application:
        - name: wordpress-http
          type: http
          target: http://127.0.0.1:9850/wp-admin/login.php
          expect:
            status: 200
          timeout: 90s
          interval: 2s

  # Resources(Kubernetes)テスト
  resources:
    checks:
      # アプリケーション検証、httpリクエストが成功することを確認
      application:
        - name: wordpress-http
          type: http
          target: http://127.0.0.1:9850/wp-admin/login.php
          expect:
            status: 200
          timeout: 90s
          interval: 2s

少し複雑に見えますが、定義する内容はいたってシンプルで、

  • 開始時点となるコンテナを定義し、
  • Container/Compose/Kubernetesそれぞれのデプロイ方式での正常性を定義する

だけです。

Promote Strategy実行

冒頭の動画はここにあたります。

raind promote strategy

これだけです。

実行時出力例
Promote Strategy: wordpress-stack
[container] create::mysql ... ok
[container] create::wordpress ... ok
[container] runtime ... ok
[container] policies::wordpress-to-mysql-tcp:3306 ... ok
[container] policies-commit ... ok
[container] checks::runtime::mysql-running ... ok
[container] checks::runtime::wordpress-running ... ok
[container] promote ... raind_promote/bottle/bottle.yaml
[container] policies-delete ... ok
[container] delete ... ok
[bottle] apply ... wordpress-stack
[bottle] checks::runtime::bottle-running ... ok
[bottle] checks::application::wordpress-http ... ok
[bottle] promote ... raind_promote/resources
[bottle] delete ... ok
[resources] apply ... .raind_promote_strategy/resources/all.yaml
[resources] checks::application::wordpress-http ... ok
[resources] delete ... ok
bottle draft: raind_promote/bottle/bottle.yaml
compose draft: raind_promote/compose/compose.yaml
resource drafts: raind_promote/resources

内部ではコンテナ/Compose/Pod(および関連サービス)が順番に起動され、定義ファイルに書かれたチェックを実行しています。

レビュー可能な設定ファイルの確認

すべてのテストが完了すると、 raind_promote 配下に設定ファイルが作成されます。

先ほどはComposeファイルを記載したので、ここではマニフェストファイルをサンプルで記載します。

manifest.yaml
# Generated by Raind Promote from a Bottlefile.
# Review generated manifests before applying them.
apiVersion: v1
kind: Namespace
metadata:
  name: "wordpress-stack"
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: "mysql-config"
  namespace: "wordpress-stack"
data:
  GOSU_VERSION: "1.19"
  LANG: "C.UTF-8"
  MYSQL_DATABASE: "wordpress-db"
  MYSQL_MAJOR: "8.4"
  MYSQL_SHELL_VERSION: "8.4.10-1.el9"
  MYSQL_USER: "wordpress-user"
  MYSQL_VERSION: "8.4.10-1.el9"
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: "wordpress-config"
  namespace: "wordpress-stack"
data:
  APACHE_CONFDIR: "/etc/apache2"
  APACHE_ENVVARS: "/etc/apache2/envvars"
  GPG_KEYS: "1198C0117593497A5EC5C199286AF1F9897469DC C28D937575603EB4ABB725861C0779DC5C0A9DE4 AFD8691FDAEDF03BDF6E460563F15A9B715376CA"
  LANG: "C.UTF-8"
  PHPIZE_DEPS: "autoconf \t\tdpkg-dev \t\tfile \t\tg++ \t\tgcc \t\tlibc-dev \t\tmake \t\tpkg-config \t\tre2c"
  PHP_ASC_URL: "https://www.php.net/distributions/php-8.3.31.tar.xz.asc"
  PHP_CFLAGS: "-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64"
  PHP_CPPFLAGS: "-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64"
  PHP_INI_DIR: "/usr/local/etc/php"
  PHP_LDFLAGS: "-Wl,-O1 -pie"
  PHP_SHA256: "66410cee07f4b2baeb0843140bb2a2b52ef930b5cf9b3d6e6d158b33aae8fa37"
  PHP_URL: "https://www.php.net/distributions/php-8.3.31.tar.xz"
  PHP_VERSION: "8.3.31"
  WORDPRESS_DB_HOST: "mysql.wordpress-stack.svc.cluster.local"
  WORDPRESS_DB_NAME: "wordpress-db"
  WORDPRESS_DB_USER: "wordpress-user"
---
# Example secret only. Replace placeholders before applying.
apiVersion: v1
kind: Secret
metadata:
  name: "mysql-secret"
  namespace: "wordpress-stack"
type: Opaque
stringData:
  MYSQL_PASSWORD: "<replace-me>"
  MYSQL_ROOT_PASSWORD: "<replace-me>"
---
# Example secret only. Replace placeholders before applying.
apiVersion: v1
kind: Secret
metadata:
  name: "wordpress-secret"
  namespace: "wordpress-stack"
type: Opaque
stringData:
  WORDPRESS_DB_PASSWORD: "<replace-me>"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: "mysql-mysql"
  namespace: "wordpress-stack"
  annotations:
    "raind.dev/reclaimPolicy": "Retain"
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: "mysql"
  namespace: "wordpress-stack"
spec:
  replicas: 1
  selector:
    matchLabels:
      app: "mysql"
  template:
    metadata:
      labels:
        app: "mysql"
    spec:
      volumes:
        - name: "mysql-mysql"
          persistentVolumeClaim:
            claimName: "mysql-mysql"
      containers:
        - name: "mysql"
          image: "mysql:8"
          command:
            - "docker-entrypoint.sh"
            - "mysqld"
          envFrom:
            - configMapRef:
                name: "mysql-config"
            - secretRef:
                name: "mysql-secret"
          ports:
            - containerPort: 3306
          volumeMounts:
            - name: "mysql-mysql"
              mountPath: "/var/lib/mysql"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: "wordpress"
  namespace: "wordpress-stack"
spec:
  replicas: 1
  selector:
    matchLabels:
      app: "wordpress"
  template:
    metadata:
      labels:
        app: "wordpress"
    spec:
      containers:
        - name: "wordpress"
          image: "wordpress:latest"
          command:
            - "docker-entrypoint.sh"
            - "apache2-foreground"
          envFrom:
            - configMapRef:
                name: "wordpress-config"
            - secretRef:
                name: "wordpress-secret"
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: "mysql"
  namespace: "wordpress-stack"
spec:
  type: ClusterIP
  selector:
    app: "mysql"
  ports:
    - port: 3306
      targetPort: 3306
      protocol: "TCP"
---
apiVersion: v1
kind: Service
metadata:
  name: "wordpress"
  namespace: "wordpress-stack"
spec:
  type: NodePort
  selector:
    app: "wordpress"
  ports:
    - port: 9850
      targetPort: 80
      protocol: "TCP"
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: "allow-wordpress-to-mysql"
  namespace: "wordpress-stack"
spec:
  podSelector:
    matchLabels:
      app: "wordpress"
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: "mysql"
      ports:
        - protocol: "TCP"
          port: 3306

パスワード関係などはマスクされていたり、実際の環境では不要なもの、NodePortではなくIngressで公開するなど、調整は必要ですが、この内容をもとにチューニングしていくような使い方が可能です。

今後のロードマップ

現時点ではPromote Strategyの土台が出来上がった段階なので、今後はより幅広いチェック方針への対応や生成ファイルの充実化、それを行うためにRaindとして対応するKubernetesのリソースタイプ拡充などを考えています。

また、MCPでの公開も検討しております。
これは、AI Agentに実際のDocker/Kubernetesを触らせることなく、アプリケーションテストに基づく設定ファイルの作成やレビューなどを行わせることができるのでは?という考えがあるためです。

最後に

今後も開発は続けていきます。
皆さんからのご意見やご要望などがあればコメントいただけると、今後の開発の参考やロードマップへの組み込みなどもできますので、ぜひぜひよろしくお願いいたします。

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?