DevOps基礎編#2:Dockerで変わる開発環境とデプロイ戦略
はじめに
皆さん、こんにちは。オアシステクノロジーズの古本です。
前回の「[第1回の記事タイトル]」に続き、DevOps基礎シリーズの第2回を始めます。
今回のテーマはDockerです。
おそらく多くのエンジニアが「docker-compose up
は使ったことがある」というレベルだと思います。しかし、0→1でアーキテクチャを検討する上では、Dockerが 「なぜ」 生まれたのか、そしてそれが開発プロセス全体にどのような革命をもたらしたのかを深く理解することが不可欠です。
この20分で、皆さんがDockerを「使う」エンジニアから、「使いこなす」アーキテクト候補へと一歩踏み出すための知識を提供します。
Docker登場以前の課題:「私の環境では動く」問題
Dockerの話をする前に、我々がどんな問題に直面していたかを思い出してみましょう。
- 開発環境の差異: AさんのPCでは動くのに、BさんのPCではライブラリのバージョンが違って動かない。
- サーバー環境の複雑化: 開発サーバー、ステージングサーバー、本番サーバーでそれぞれ微妙にOSやミドルウェアのバージョンが異なり、デプロイしたら動かなくなった。
- セットアップの煩雑さ: 新しいメンバーがチームに参加するたびに、半日以上かけて開発環境を構築する必要があった。
これらの問題の根源は 「環境の差異」 です。この差異をなくし、「どこでも同じように動く」を実現するために登場したのがDockerです。
Dockerとは?:コンテナという革命
Dockerを一言で言うなら 「アプリケーションをその実行環境ごとパッケージングする技術」 です。
公式ドキュメントより引用
サーバー仮想化技術の変遷:VMからコンテナへ
Dockerの革命性を理解するために、サーバー技術がどのように進化してきたかを見てみましょう。
- 物理サーバー時代: 1台の物理マシンに1つのOSと1つのアプリケーション。リソース効率が悪く、サーバーの数が増え続ける問題がありました。
- 仮想マシン(VM)時代: 「ハイパーバイザー」という技術により、1台の物理マシン上に複数の「ゲストOS」を起動できるようになりました。これによりリソース効率は大幅に向上しましたが、各VMは完全なOSを含むため、起動が遅く、ディスクやメモリの消費が大きいという課題が残りました。
- コンテナ時代 (Docker): ホストOSのカーネルを共有し、アプリケーションとその依存関係のみをパッケージ化します。ゲストOSが不要なため、非常に軽量・高速に起動し、リソース消費も少ないのが特徴です。
VMとコンテナの違いを図で示すと、以下のようになります。
https://y-ohgi.com/introduction-docker/1_introduction/vm-docker/
引越しに例えると分かりやすいです。
- 旧来の方法: 引越し先(本番環境)に家具(ライブラリ)や家電(ミドルウェア)を一つずつ運び込み、現地で組み立てる。組み立て方を間違えたり、部品が足りなかったりするリスクがある。
- Dockerの方法: 引越し元(開発環境)で、必要なもの全てをコンテナ(段ボール)に詰めてしまう。引越し先では、そのコンテナを開けるだけで、全く同じ環境が再現される。
この「コンテナ」という考え方が革命的でした。
重要な3つの要素
Dockerを理解するために、最低限この3つを覚えましょう。
- Dockerfile : コンテナの設計図。OSイメージをベースに、どのミドルウェアを入れ、どのアプリケーションコードを配置し、どうやって起動するかを記述したテキストファイルです。インフラの構成をコードで管理(Infrastructure as Code) する第一歩です。
- Docker Image : Dockerfileを元に作成された、アプリケーションと環境のスナップショット。一度ビルドすれば、このイメージは変更されません(Immutable)。このイメージを共有することで、誰でも同じ環境を再現できます。
- Docker Container : Docker Imageを実行した状態のもの。実際にアプリケーションが動いているプロセスです。一つのイメージから、何個もコンテナを起動できます。
この3つの関係性を図にすると、以下のようになります。
https://qiita.com/yuuki_0524/items/77d4bb16ca67bb502ee7
Dockerfileを書いてみよう
百聞は一見に如かず。簡単なNode.jsアプリケーションを動かすためのDockerfileを見てみましょう。
# 1. ベースとなるイメージを指定
FROM node:18-alpine
# 2. コンテナ内での作業ディレクトリを指定
WORKDIR /usr/src/app
# 3. 必要なファイルをイメージにコピー
COPY package*.json ./
RUN npm install
COPY . .
# 4. コンテナがリッスンするポートを指定
EXPOSE 3000
# 5. コンテナ起動時に実行するコマンド
CMD [ "node", "server.js" ]
たったこれだけです。このファイルがあるだけで、誰でも docker build
と docker run
コマンドを実行すれば、あなたの作ったNode.jsアプリを全く同じ環境で動かすことができます。
基本的なコマンド
-
イメージのビルド:
docker build -t my-node-app .
-
コンテナの実行:
docker run -p 8080:3000 -d my-node-app
-
-p 8080:3000
: ホストOSの8080番ポートを、コンテナの3000番ポートに繋ぐ -
-d
: バックグラウンドで実行
-
-
実行中のコンテナ一覧:
docker ps
-
作成したイメージ一覧:
docker images
アーキテクト視点でのDockerの価値
さて、ここからが本題です。なぜアーキテクトを目指す皆さんがDockerを深く知るべきなのでしょうか。
1. 環境の完全な標準化
開発、テスト、ステージング、本番の全ての環境で全く同じイメージを使い回すことができます。これにより、「私の環境では動いたのに」という問題は撲滅され、デプロイの成功率が劇的に向上します。これはアプリケーションの品質と信頼性を担保する上で極めて重要です。
2. CI/CDパイプラインの主役:ビルドからデプロイまで
CI/CD(継続的インテグレーション/継続的デリバリー)とは、コードの変更を自動的にテストし、本番環境まで安全にリリースするための仕組みです。このパイプラインの中心で「成果物」として流れ、品質を担保するのがDockerイメージです。
具体的な流れをステップごとに見ていきましょう。
CI (Continuous Integration) フェーズ:信頼できる成果物(Dockerイメージ)を作る
CIの目的は、コードの変更を頻繁に統合し、問題を早期に発見することです。Dockerはここで「信頼できる成果物」を構築する役割を担います。
-
コードのPush: 開発者が機能開発を終え、
main
ブランチなどにコードをPushします。 - CIサーバーの起動: GitHub ActionsやJenkinsなどのCIサーバーがPushを検知し、ジョブを開始します。
-
Dockerイメージのビルド: CIサーバーはリポジトリ内の
Dockerfile
を読み込み、docker build
コマンドを実行します。これにより、アプリケーション、ライブラリ、OS設定など、全てを含んだDockerイメージが作成されます。- ポイント: ここで作成されるイメージが、後のテストやデプロイで一貫して使われる「唯一の成果物」となります。
-
自動テストの実行: ビルドしたイメージを元にコンテナを起動 (
docker run
) し、単体テストや結合テストを自動実行します。データベースが必要なテストであれば、Docker Composeを使ってテスト用のDBコンテナを同時に起動することも簡単です。- ポイント: テストも「本番と全く同じ環境(のイメージ)」に対して行うため、テストの信頼性が格段に向上します。
-
コンテナレジストリへのPush: 全てのテストが成功したら、そのイメージが「リリース可能」であると判断できます。
docker tag
コマンドでGitのコミットハッシュなどの一意なタグを付け、Amazon ECRやDocker Hubといったコンテナレジストリにdocker push
します。
CD (Continuous Delivery) フェーズ:成果物を安全かつ迅速に本番へ
CDの目的は、CIフェーズで作成された信頼できる成果物を、ボタン一つ、あるいは自動で本番環境にリリースすることです。
-
デプロイのトリガー:
main
ブランチへのマージが成功したら自動でデプロイを開始する、あるいは手動でデプロイボタンを押すなど、設定されたトリガーでCDプロセスが始まります。 -
新しいイメージのPull: 本番環境のサーバー(またはECSやKubernetesといったコンテナオーケストレーションツール)が、コンテナレジストリから先ほどPushされた新しいバージョンのDockerイメージを
docker pull
します。- ポイント: ここではソースコードからのビルドは一切行いません。CIが作成したテスト済みのイメージをそのまま持ってくるだけなので、高速かつ安全です。
-
コンテナの入れ替え(デプロイ戦略): 新しいイメージを元にコンテナを起動し、古いバージョンのコンテナと入れ替えます。ここでアーキテクトとして、サービスの特性に合わせたデプロイ戦略を選択します。
- ローリングアップデート: 新しいコンテナを1台起動→正常性を確認→古いコンテナを1台停止、というプロセスを繰り返し、ダウンタイムなしで徐々に入れ替える方式。
- Blue/Greenデプロイ: 新バージョン(Green)の環境一式を、現行バージョン(Blue)とは別に用意。テスト後にロードバランサーの向き先を一度にGreenに切り替える。問題があれば即座にBlueに戻せるのが利点。
このように、CI/CDプロセス全体を通してDockerイメージという一貫した「パッケージ」が受け渡されていくことが、現代のアプリケーション開発の速度と信頼性を支えています。アーキテクトは、この流れ全体を設計し、最適化する重要な役割を担います。
3. マイクロサービスアーキテクチャの実現
将来的に大規模なサービスを構築する際、モノリシックな構成ではなく、機能ごとにサービスを分割するマイクロサービスアーキテクチャが選択肢になります。
Dockerは、各サービスを独立したコンテナとして開発・デプロイすることを容易にし、マイクロサービスの実現を強力に後押しします。
まとめと次回予告
今回のまとめです。
- Dockerは「環境の差異」という古くからの課題を解決した。
-
Dockerfile
でインフラをコード化し、誰でも同じImage
を作れる。 - アーキテクトにとってDockerは、 環境の標準化 、 CI/CDの中核 、 マイクロサービスの基盤 として極めて重要な技術である。
Dockerは単なる便利ツールではありません。アプリケーションのライフサイクル全体を設計するための 戦略的な武器 です。
次回は、今回作成したDockerイメージを使って、 「GitHub ActionsによるCI/CDパイプラインの構築」 をテーマにお話しします。お楽しみに。
Discussion