現実の問題:サーバーと一緒にアプリが落ちるとき
以前、小規模なECサイトのプロジェクトをデプロイしていたことがある。単一のVPS上でDockerだけを使って動かしていたのだが、サーバーのRAMが限界を超えた瞬間、深夜にアプリが突然クラッシュした。自動で再起動する仕組みもなかった。さらに悪いことに、マイクロサービスに移行した後、あるコンテナでメモリリークが発生し、原因の特定だけで2日近くかかってしまった。全体を俯瞰できるビューがなく、どのコンテナがホスト上で異常にリソースを消費しているのか把握できなかったのが大きな原因だった。
さらに、トラフィックが急増するたびに、手動でサーバーにSSH接続し、docker runでコンテナを追加し、nginxを再設定するという作業を繰り返していた。時間がかかる上に、ミスも起きやすかった。
心当たりはないだろうか。単一サーバー、手動スケーリング、フェイルオーバーなし。これこそ、コンテナオーケストレーションを真剣に考えるべきタイミングだ。
原因分析:なぜDockerだけでは不十分なのか?
Dockerはアプリケーションのパッケージングと実行に非常に優れているが、本番環境における3つの核心的な課題を解決できない。
- 単一障害点(SPOF):サーバーが1台落ちると、そこで動くコンテナもすべて道連れになる。
- 手動スケーリング:あるサービスを5つのレプリカで動かしたければ、自分で
docker runを5回実行しなければならない。 - 自動ロードバランシングなし:複数のインスタンスを起動しても、コンテナ間でトラフィックを自動的に分散する仕組みがない。
Dockerの本質は、単一ホスト上のコンテナ管理だ。複数のマシンを連携させて動かしたいなら、その上にオーケストレーション層が必要になる。
解決策の選択肢:何を選ぶべきか?
コンテナオーケストレーションといえば、すぐに3つの名前が挙がる:
- Docker Swarm:Docker Engineに組み込み済みで、セットアップが簡単、おなじみの構文が使える。小規模チームや中規模プロジェクトに適している。
- Kubernetes(K8s):最も強力だが、学習曲線が急で、運用に必要なリソースも多い。
- Nomad(HashiCorp):柔軟で非コンテナワークロードにも対応しているが、他の2つほど普及していない。
オーケストレーションを初めて学ぶなら、Swarmが最も合理的な出発点だ。追加インストール不要、使い慣れたdocker-composeファイルをそのまま活用でき、中小規模プロジェクトのユースケースの90%をカバーできる。
ベストアプローチ:Docker Swarmを段階的に始める
まず押さえておくべき基本概念
これから何度も登場する3つの概念を理解しておこう:
- ノード:クラスターに参加するサーバー。manager(調整・意思決定担当)とworker(コンテナの実行担当)の2種類がある。
- サービス:Swarmにデプロイするアプリケーション。イメージ、レプリカ数、ポート、リソース制限などを定義する。
- タスク:ノード上で実際に動作しているコンテナ。サービスの各レプリカが1つのタスクに相当する。
ステップ1:Swarmクラスターの初期化
managerマシン(メインサーバー)上で以下のコマンドを実行する。192.168.1.100は実際のIPアドレスに置き換えること:
# Swarmを初期化する
docker swarm init --advertise-addr 192.168.1.100
実行すると、workerをクラスターに追加するためのコマンドが表示される:
docker swarm join --token SWMTKN-1-xxxxx... 192.168.1.100:2377
このコマンドをworkerマシンで実行するだけで、ノードが自動的にクラスターに参加する。後でトークンを再取得したい場合は:
docker swarm join-token worker
ステップ2:クラスターの状態確認
# ノード一覧を表示する
docker node ls
# 出力例:
# ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
# abc123 * manager01 Ready Active Leader
# def456 worker01 Ready Active
ステップ3:最初のサービスをデプロイする
docker runの代わりにdocker service createを使う:
# nginxを3レプリカでデプロイし、ポート80を公開する
docker service create \
--name my-nginx \
--replicas 3 \
--publish published=80,target=80 \
nginx:alpine
# 実行中のサービスを確認する
docker service ls
docker service ps my-nginx
Swarmがクラスター内のノードに3つのコンテナを自動的に分散させ、それらの間でロードバランシングを行う。
ステップ4:コマンド1つでサービスをスケールする
# レプリカを3から5に増やす
docker service scale my-nginx=5
# またはupdateコマンドを使う
docker service update --replicas 5 my-nginx
Swarmがリソースに余裕のあるノードを自動的に見つけ、追加のコンテナを起動する。手作業不要、各マシンへのSSH接続も不要だ。
ステップ5:Docker Stackでデプロイする(実践的な方法)
本番環境ではDocker Stackを使う。docker-composeと同等だが、Swarm上で動作する。docker-stack.ymlファイルを作成しよう:
version: '3.8'
services:
web:
image: nginx:alpine
ports:
- "80:80"
deploy:
replicas: 3
restart_policy:
condition: on-failure
update_config:
parallelism: 1
delay: 10s
api:
image: my-api:latest
deploy:
replicas: 2
resources:
limits:
memory: 512M
# StackをSwarmにデプロイする
docker stack deploy -c docker-stack.yml my-app
# stack内のサービスを確認する
docker stack services my-app
# サービスのログを確認する
docker service logs my-app_web
ステップ6:ダウンタイムなしのローリングアップデート
本番環境で最もよく使う機能がこれだ:アプリを止めずにサービスをアップデートできる。Swarmはコンテナを1つずつ更新し、常にトラフィックを処理しているインスタンスが存在することを保証する:
docker service update \
--image nginx:1.25-alpine \
--update-parallelism 1 \
--update-delay 10s \
my-nginx
本番環境に影響を与えずにノードをメンテナンスする
workerをメンテナンスする前に、drainして他のノードにタスクを移動させる:
# ノードをdrain状態にする(新しいタスクを受け付けず、既存のタスクは移動される)
docker node update --availability drain worker01
# メンテナンス完了後、ノードをactiveに戻す
docker node update --availability active worker01
実体験から得たヒント
冒頭で話したメモリリークの件に戻ると、Swarmに移行してからdocker service ps my-app_apiコマンドのおかげで原因をはるかに素早く特定できた。どのタスクが繰り返し再起動しているかがすぐにわかり、そこから特定のコンテナのログを追跡できた。単体のDockerのままだったら、あのような全体俯瞰ビューは得られなかっただろう。
実際にSwarmを運用する中で学んだこと——その多くは失敗から——をいくつか紹介する:
- リソース制限は必ず設定する:スタックファイルの
resources.limits.memoryを設定し、1つのサービスがノード全体のRAMを食い尽くすのを防ぐ。 - 本番環境ではmanagerノードを最低3台に:1台のmanagerに障害が発生してもクォーラムを維持するため(SwarmはRaftコンセンサスを使用)。
- オーバーレイネットワークは自動構成される:同じstack内のサービス同士はサービス名で通信でき、ノード間の通信を暗号化したい場合は追加のネットワーク設定も検討しよう。
- 環境変数の平文書きの代わりにDocker Secretsを使う:サーバーへの不正アクセスが発生した場合でも、平文の認証情報が露出するリスクを最小化できる。
# シークレットを作成する
echo "my_db_password" | docker secret create db_password -
# スタックファイル内での使い方:
# secrets:
# db_password:
# external: true
まとめ
Docker Swarmは、単一ホストのDockerでは解決できない課題を克服する:高可用性、自動スケーリング、そしてほぼゼロダウンタイムのローリングアップデートだ。セットアップはわずか数分で完了し、docker-composeに慣れていれば追加で学ぶことはほとんどない。
Kubernetesが必要になるのはいつか?メトリクスに基づく自動スケーリング、きめ細かいRBAC、複雑なGitOpsワークフローが本当に必要になったときだ。その手前の段階では、Swarmで十分対応できる——そしてはるかにシンプルだ。