WebアプリのDocker化についての要点

はじめに

言語関係なくWeb開発環境のDocker化で必要な事項についてまとめていきたいと思います。

Web開発においてはコンテナを単体で動かすより、複数のコンテナを連動させながら動かすことのほうが多いため、 オーケストレーションツール(docker-compose)についてもまとめていきます。

Dockerfile作成

Dockerを用いた独自の仮想環境を構築するためにはDockerfileが必要不可欠です。
作成するうえで必要な内容について記述していきます。

Dockerfile作成に必要な内容としては下記があります。

  • DSL: ドメイン固有言語 / ドメイン特化言語としてDockerfileのコマンドについて説明します
  • セキュリティ: Dockerfileを記述する上でのセキュリティやコンテナ技術のセキュリティについて説明します
  • ベストプラクティス: Dockerでの環境運用を行う上で良しとされている方法について説明します

生成コマンド

2023/12下旬現在ベータ版ではありますが、下記コマンド実行を行うことで 必要なファイルの生成ができます。

・Dockerfile
・.dockerignore
・compose.yaml
・README.Docker.md

docker init [OPTIONS]
# OPTIONSにはinit プラグインのバージョンを表示する--versionがあります。


コマンドを打つと言語を選ぶような質問が返ってくるためその中から選択を行います。

選択肢としては下記があります。
- Golang
- Node.js
- Python
- PHP(Apacheを利用した)
- Rust
- ASP.NET
- その他
現在は実稼働環境でそのファイルを利用することは非推奨とされているので、
本番環境では利用しないようにしてください。

逐一ドキュメントで確認することをお勧めします。

docs.docker.com

DSL

Dockerfileには非推奨のもの含め18のコマンドが存在します。
Dockerfileリファレンス 日本語
ちなみに、DSLとはドメイン固有言語 / ドメイン特化言語という意味合いであり、特段Dockerfileで記述するコマンドのことのみを指す言葉ではありません。

コマンドまとめ

コマンドのリンクからは日本語のドキュメントに遷移できます。
構文はリンク先からご確認ください。

コマンド 説明
FROM 以降の命令で使うベースイメージを指定します。
正しいDockerfileはFROMから開始する必要があります。Dockerhubにアップロードされている公式のイメージファイルを使うのが一般的です。
CMD コンテナ起動時に記述したコマンドを実行し、フォアグラウンドで実行されている間が生存期間になります。
CMD の主な目的は、コンテナ実行時のデフォルト(初期設定)を指定するためです 。
実行ファイルを含まない場合は、 ENTRYPOINT 命令の指定が必要です。
ENV コンテナ内で使用する環境変数を定義します。
キーと値で設定します。
途中で書き換えも可能です。
維持すると悪影響を及ぼすこともあるため、
不都合がある際はRUNコマンドで値の指定を行います。
WORKDIR Dockerfileでコマンド実行する際に基準となるディレクトリを設定します。
デフォルトは'/'です。最悪の場合既存のディレクトリを上書きする可能性があるため必ず指定します。
COPY ホストからコンテナ内にファイルやディレクトリをコピーします。
ホスト側のディレクトリはdocker buildで指定したディレクトリとなります。
コンテナ側はWORKDIRで指定したディレクトリとなります。
RUN コンテナ内でコマンドを実行します。Linuxだとデフォルトはsh。
&&でつなぐことでレイヤーが減るためそうしない場合より速くなります。
パッケージのインストールや権限、ユーザー設定などを行います。
不要なパッケージのキャッシュを削除するコマンドも各コマンドと組み合わせて使いましょう。
rm -rf /var/lib/apt/lists/* && rm -rf /var/cache/yum/*
USER コンテナで実行するユーザーを設定します。
rootがデフォルトとなっているため、セキュリティの観点からも必ず指定をしてください。
EXPOSE コンテナ内で実行するポートの指定を行います。
ホスト側へ公開する場合は-Pでポート番号を指定する必要があります。
なお、docker-composeで起動する場合はportsによる指定で開放します。
VOLUME Data Volumeを作成するためのコマンドです。
ボリュームとはデータを永続化させるものです。
基本的にはログのような更新頻度の激しいファイルで使用します。
ARG ビルド時に変数を使用するためのコマンドです。
デフォルト値を設定することができます。
設定以後、その変数を利用することができます。
構築ステージが終了するまで利用できます。
変数を渡すには、構築時に docker build コマンドで --build-arg <変数名>=<値> を指定します。
指定しなかった場合は警告されます。
ADD 指定したパスやURLのファイルをコンテナ内にコピーするコマンド。
認識できる圧縮形式( 無圧縮identity 、 gzip 、 gzip2、xz )の tar アーカイブの場合はディレクトリとして展開されます。
ENTRYPOINT 指定されたコマンドを実行します。
CMDとは異なり、docker run時に引数を指定することができます。
また、実行ファイル形式で処理するように設定することもできます。
LABEL キーとバリュー構成されるメタデータを付与することができます。
docker imageでは複数のLABELを持つことができ、1行で複数設定することができます。
ONBUILD 次のビルド時に利用するコマンドを設定することができます。
親イメージでONBUILDを設定していると、それをFROMに指定した子イメージをビルドした際、FROM直後にONBUILDで指定したコマンドが動作します。
STOPSIGNAL コンテナが終了するために送信するシステムコールシグナルを設定します。
カーネルの syscall 表と一致します。
HEALTHCHECK コンテナが停止していないかどうか確認する、もしくは親イメージからのHEALTHCHECKを無効化するためのコマンドです。
サーバのプロセスが実行中の無限ループ詰まりや新しい接続を処理できなかったりするような問題を検出することができます。
使用するとhealth statusが追加されます。
health statusの状態はstarting, healthy, unhealthyの3つがあります。
SHELL コマンドで利用するシェル形式において用いられるデフォルトのシェルを上書き設定するために利用します。
MAINTAINER(非推奨) ビルドするイメージのAuthorフィールドを設定できます。
ただこちらは削除予定のため覚える必要はないかと思います。
今後はLABELで設定します。

セキュリティ

ユーザー指定する

ユーザー指定を行わない場合、コンテナの実行はrootユーザーで行われます。
不正アクセスやコンテナ漏洩のリスクを下げるためにもUSERの設定を行いましょう。

信頼できるイメージを使用する

Docker Hubの画面上にバッジが幼児されている下記から選ぶのが比較的安全です。

  1. Docker Official Image
    Docker Hub上で最も信頼できるイメージ群です。
    ベースOSやプログラミング言語ミドルウェアなどのイメージが含まれます。

  2. Verified Publisher
    Dockerによって検証された発行者が作成したイメージ群です。
    各種エージェントプログラムが含まれます。
    例としてaws-cliのようなツールがあります。

  3. Sponsored OSS
    DockerがスポンサードしているOSSプロジェクトによって公開されているイメージ群です。
    Dockerが検証を行っているため安心して利用できます。

.dockerignoreファイル

.envファイルのような機密情報を扱うファイルをコンテナ内に含めないようにしましょう。
記述は.gitignoreと同じくファイルパスを記述します。

ソフトウェアの脆弱性

コンテナイメージ作成後はイミュータブルなものとして扱われるため、定期的に脆弱性の診断を行い、
ローカル環境で脆弱性を解消し、再ビルドと再デプロイを行う必要があります。

latestタグを使用しない

オートスケール時などコンテナが作成・再作成されるタイミングを完全に把握することは難しいため、
latestタグのイメージを利用している場合は、その各コンテナでアプリケーションバージョンが異なってしまう場合があります。
その際、後方互換性がないバージョンだと障害に繋がってしまう可能性があります。
また、Docker Hubなどのレジストリでは同じタグに再プッシュが可能なため、アカウントの乗っ取りがあった場合に悪意のあるイメージをプッシュされる可能性があります。
これを防ぐためにセマンティックバージョニングを用いたバージョン設定やハッシュ値を用いたバージョン設定をすることが推奨されています。

ベストプラクティス

扱いやすいイメージを作るための「すべき」「したほうが良い」という項目について取り上げます。
ただ、「しなければならない」ではないため、場合によって使い分けていきましょう。

1プロセス1コンテナ

各コンテナはただ1つだけの用途を持つべき

ベストプラクティスにはそのように記載されています。
これはコンテナを簡単にスケールさせたり再利用性を高めたりするため、アプリケーションを複数のコンテナに切り離すものです。
必須ではないですが、水平スケールや再利用性を高めたい場合は採用しましょう。

軽量なイメージを作成する

Docker Imageは軽量であればあるほど良いとされています。
そのため不要なものは入れず、軽量なものを使っていくという方針を取ります。
その中で有用な方法をまとめます。

alpineを使用する

Linuxディストリビューション環境を利用する場合はAlpine Linuxを使用することで軽量化することが望めます。
例えばLinuxディストリビューションの中で人気のあるUbuntuと比較した際、
Ubuntuイメージは約80MBですが、Alpine Linuxは約7MBほどです。

distrolessという選択肢

distrolessとはGoogleが提供している軽量なコンテナイメージ群の名称です。
動作させたいアプリケーションとその実行に必要な依存関係のみを入れて使うことを前提としているため、 最小限のファイルしか存在せず、シェルすらも含まれていません。
その結果、軽量化とセキュリティの向上が期待できます。

レイヤの最小化

RUN、COPY、ADD命令はイメージ全体の実容量を増やします。
ほかの命令は構築時の一時的な中間イメージで使われますが、イメージの全体の容量には影響を与えません。
下記のように1コマンドごとに1つのRUNを準備するとその分レイヤが増えていくことになります。

RUN apt-get update
RUN apt-get install git ~

それを && でつないで(改行時は「\」)、なるべくひとつのレイヤにまとめます。

RUN apt-get update && \
    apt-get install git ~

ただ、後述のマルチステージビルドを使って最終的に利用するイメージの容量やレイヤ数を減らすのも1つの手となります。

最小限の構成にする

不要なパッケージは入れない

環境構築に際して必要なもののみ入れるようにしましょう。
「あったほうが良いかも」程度のパッケージのインストールは行いません。
そういったパッケージが増えることで依存関係の複雑さが発生し、イメージの容量が増えることに繋がります。

Debian環境ではapt-getコマンドを利用する場合は--no-install-recommendsオプションを付けたイメージ構築を行うことで、 関係する推奨パッケージの自動インストールをしないようになります。

マルチステージビルド

ひとつのDockerfile内で複数のFROMを記述することで、段階的にイメージのビルドができる機能です。
セキュリティやレイヤ数の観点からも利用が推奨されています。
Golangのようなビルドでバイナリを出力するような言語でマルチステージビルドを行うことで軽量化が見込めます。
開発・検証環境ではソースコードや依存関係、開発ツール群を含め、本番環境ではバイナリのみといった形を構築することも可能です。

下記、docker docsからの引用です。

# syntax=docker/dockerfile:1
FROM golang:1.16 AS builder
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html
COPY app.go    ./
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /go/src/github.com/alexellis/href-counter/app ./
CMD ["./app"]

最初のFROM golang:1.16 AS builderで設定したエイリアスを使い、
下部のCOPY --from=builder /go/src/github.com/alexellis/href-counter/app ./の--from=builderで利用しています。

オーケストレーションツール導入

オーケストレーションツールとは

複数のコンテナを効率的に管理するためのツールです。
コンテナ間通信(ネットワーク)やオートスケールといった機能を備えています。 例としてdocker-composeKubernetesがあります。

利便性の高さからWeb開発ではDockerと抱き合わせで利用されることが多いです。

下記ではdocker-composeについて記載していきます。

compose.ymlファイル

docker-composeを用いたオーケストレーションを行うためにYAML形式(compose.yml)のファイルを用います。
ここではよく使うものに絞って記述いたします。それ以外の設定についてはこちらからご確認ください。

記述方法

キー 説明
services 起動するコンテナの定義を行います。
サービス名は任意の名前を設定することができます。
volumes ボリュームを作成するための記述です。
ここで定義したボリュームはサービス内のvolumesで利用することができます。
※ボリュームとはコンテナ内のデータを永続化するためのもので名前付きと匿名ボリュームがあります。
networks コンテナ間通信について独自のネットワーク設定を行うことができます。
networks以下のインテントでネットワーク名を記述し、さらにそれ以下にネットワークのドライバなどの設定を行います。

サービス以下のインデントで使用する設定はこちらです。

キー 説明
image イメージの指定を行います。
イメージが存在しない場合、Composeはpullを行います。
container_name コンテナ名を指定します。
コンテナ名の重複はできないため、指定した場合は複数コンテナにスケールできなくなります。
build ビルド時に適用するオプションを設定します。
ビルドするDockerfileのパスを指定したり、引数の設定を行ったり、ビルド関連の設定ができます。
volumes ボリュームのマウントを指定します。
コンテナとホストの間でデータ共有するために設定します。
environment 辞書形式で環境変数の設定を行うことができます。
ports ポートの設定を行うことができます。
ホスト側ポート番号:コンテナ側で設定を行い、公開用のポート番号を設定します。
depends_on サービス間の依存関係を設定します。
docker compose upを行った際にこの項目に記載があるコンテナを先に起動します。
docker compose stopの際は依存するサービスの前に、設定したサービスを停止します。
command コマンドを記載すると、Dockerfileに記載されているコマンドを上書きしてコマンド実行します。
healthcheck コンテナが正常かどうか判断するために実行するコマンドを記載します。
確認の間隔や試行回数など細かく設定することが可能です。

それ以外にも便利な設定が多く存在するのでリファレンスで確認してみてください。

docs.docker.jp