コンテナ技術が変えた「アプリケーションの動かし方」
「自分のマシンでは動くのに、本番サーバーだと動かない」。エンジニアなら一度は経験するこの問題を、Dockerはほぼ解決してしまいました。アプリケーションとその実行環境をまるごと「コンテナ」という単位にパッケージングすることで、開発環境と本番環境の差異を極限まで減らせるのです。
しかし、コンテナが10個、100個、1000個と増えていくと、今度は「どのサーバーで動かすか」「落ちたら自動で再起動させたい」「トラフィックに応じてスケールさせたい」という新たな課題が生まれます。そこに登場したのがKubernetesです。
この記事では、DockerとKubernetesをペアで理解し、実務で使いはじめるための考え方を整理します。
Dockerの基本:イメージとコンテナ
Dockerを理解するうえで最も重要な概念が「イメージ」と「コンテナ」の区別です。
イメージとは、アプリケーションを動かすために必要なすべてのもの(OS、ランタイム、ライブラリ、アプリのコード)を含んだ読み取り専用のテンプレートです。設計図と考えると分かりやすいでしょう。コンテナはそのイメージを実際に起動した実行インスタンスです。一つのイメージから複数のコンテナを起動できます。
イメージはDockerfileという設定ファイルから作ります。
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
この数行で、Pythonアプリの実行環境がどこでも再現できるようになります。FROMで土台となるイメージを指定し、RUNでパッケージをインストールし、COPYでソースを取り込む——という流れは、多くのDockerfileに共通するパターンです。
レイヤーキャッシュを意識した書き方
Dockerfileは命令の一つひとつがレイヤーになり、変更がなければキャッシュが使われます。このため、COPY requirements.txtとpip installを先に書き、その後でソースコードをコピーするのがベストプラクティスです。ソースコードだけを変更した場合、依存関係のインストールがキャッシュから再利用されてビルド時間が大幅に短縮されます。逆に、ソースコードを先にコピーしてしまうと、コードを1行変えるたびに全パッケージの再インストールが走ってしまいます。
Kubernetesの基本:Podからはじめよう
Kubernetesの設計思想は「宣言的な構成管理」にあります。「このアプリを3台動かしておいて」と宣言すると、Kubernetesが自律的にその状態を維持し続けます。1台落ちれば自動で補充し、負荷が上がればスケールアウトする——これが「オーケストレーション」の本質です。
Kubernetesには多くの概念が登場しますが、最初に押さえるべきは以下の4つです。
| リソース | 役割 | 例え |
|---|---|---|
| Pod | コンテナの最小実行単位 | アプリが動く小部屋 |
| Deployment | Podの数や更新戦略を管理 | Podを管理する班長 |
| Service | Podへの安定したアクセス窓口 | 受付・ロードバランサー |
| Namespace | リソースを論理的に分離する仕組み | テナントの仕切り |
最小の構成として、DeploymentとServiceを組み合わせてアプリを公開する例を見てみましょう。
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: myrepo/my-app:1.0.0
ports:
- containerPort: 8000
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
replicas: 3が宣言的設定の象徴です。「3つ動かせ」と書けば、Kubernetesがそれを実現し維持します。resourcesフィールドでCPU・メモリの使用量を明示することも重要です。制限を設けないと、ひとつのコンテナが暴走してノード全体に影響する、いわゆる「ノイジーネイバー問題」が発生します。
ローリングアップデートとゼロダウンタイムデプロイ
Kubernetesを使う大きなメリットのひとつが、ダウンタイムなしでアプリを更新できることです。Deploymentのデフォルト動作は「ローリングアップデート」で、古いPodを少しずつ新しいPodに入れ替えていきます。
更新戦略はstrategyフィールドで細かく制御できます。
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1 # 同時に停止できるPodの最大数
maxSurge: 1 # 上限を超えて同時に起動できるPodの最大数
この設定では、3台構成のアプリを更新する際に、常に最低2台は動き続けます。ユーザーはアップデートに気づかないまま、新バージョンに切り替わっていきます。
問題のあるバージョンをデプロイしてしまった場合は、kubectl rollout undo deployment/my-appの一行でひとつ前のバージョンに即座に戻せます。これも宣言的管理の恩恵です。
実務での落とし穴と対処法
理解が進むと実装が始まりますが、実務ではいくつかのつまずきポイントがあります。
まずイメージタグにlatestを使わないことです。latestは常に最新を指すため、いつビルドされたものかが不明確になります。my-app:1.2.3やmy-app:git-abc1234のように、バージョンやコミットハッシュを含めたタグを必ずつけましょう。
次にConfigMapとSecretの使い分けです。データベースのホスト名などの設定値はConfigMapに、パスワードやAPIキーはSecretに格納します。SecretはBase64エンコードされているだけで暗号化はされていないため、本番環境ではAWS Secrets ManagerやHashiCorp Vaultなどと連携する外部シークレット管理ツールの利用を検討してください。
| 設定の種類 | 推奨する保存場所 |
|---|---|
| アプリの設定値(ホスト名、ポートなど) | ConfigMap |
| 機密情報(パスワード、APIキー) | Secret + 外部シークレット管理 |
| 大容量データや証明書ファイル | Volume / PersistentVolumeClaim |
まとめ
DockerとKubernetesは、現代のアプリケーション開発・運用を支える事実上の標準インフラです。Dockerで環境の再現性を確保し、Kubernetesでスケールと高可用性を実現する——この組み合わせを理解することは、バックエンドエンジニアにとってもインフラエンジニアにとっても、今や必須のスキルと言えます。
最初は概念の多さに圧倒されますが、まずはローカルでminikubeやkind(Kubernetes in Docker)を使って手を動かしてみることをお勧めします。YAMLを書いてPodを動かし、Serviceを公開する体験を積むことで、本番環境への理解が一気に深まります。