🖥️

【3-1】Docker入門編!なぜコンテナが必要か?UbuntuへのインストールとAD連携Sudo沼

に公開

はじめに

前回の記事では、Proxmox VEによる仮想化基盤を構築し、Windows ServerでActive Directoryを立ち上げ、Ubuntu Serverをドメインに参加させるというハイブリッド環境の基礎を固めてきました。いよいよ、現代のアプリケーション開発・運用に欠かせない技術であるDockerの学習にステップインします!
https://zenn.dev/koikoi_infra/articles/13b5b4cb70921d

この記事では、これまでの学習の総仕上げとして、ドメインに参加したUbuntu Server上にDocker環境を構築するプロセスを記録します。Dockerの基本的な概念の整理から、具体的なインストール手順、そしてAD連携環境ならではの「Sudo沼」にハマり、解決するまでの道のりを共有します。

今回のゴール

Dockerとは何か? - 3つのキーワードで理解する

Dockerが解決する「環境が違うと動かない問題」

まず、なぜDockerが必要とされているのでしょうか?それは、多くの開発者が経験する「自分のPCでは動いたのに、サーバーに持っていくと動かない…」という問題を解決するためです。

この問題は、OSのバージョンの違い、インストールされているライブラリの違い、設定ファイルの記述ミスなど、開発環境と本番環境のわずかな「環境の差異」によって引き起こされます。

解決策は「アプリと環境のパッケージ化」

Dockerは、この問題を解決するために非常に賢いアプローチをとります。それは、「アプリケーション本体」と、そのアプリが動くために必要な「OSのライブラリや設定ファイルなどの実行環境」を、丸ごと一つのパッケージにしてしまうというアイデアです。

このパッケージを、開発PCでも本番サーバーでも同じように動かすことで、「環境の差異」そのものを無くしてしまおうというわけです。そして、この仕組みを実現するために、Dockerには3つの重要なキーワードが登場します。

パッケージ化を実現する3つの要素

1. Dockerfile:アプリの動かし方を書く「レシピ」

まず、Dockerは「どういう環境で、どういう手順でアプリを動かすか」を記述した設計図を必要とします。これがDockerfileと呼ばれるテキストファイルです。料理に例えるなら、これは「レシピ」そのものです。

2. イメージ:どこでも同じ味が再現できる「ミールキット」

次に、Dockerfile(レシピ)をもとに、実際にアプリケーションを実行できる状態のパッケージを作成します。これが「イメージ」です。これは、レシピをもとに材料がすべて揃えられ、下ごしらえまで済んだ「ミールキット」のようなものです。このイメージさえあれば、誰がどこで作っても同じ品質のものが出来上がります。

3. コンテナ:実際に動いている「料理」

最後に、出来上がった「イメージ」(ミールキット)を実際に動かします。この、実行中の状態が「コンテナ」です。これは、ミールキットを調理して食卓に出された「出来上がった料理」です。

この「Dockerfile → イメージ → コンテナ」という流れが、Dockerの基本です。

改めて、これまでに学んだVMやLXCと比較すると、その目的の違いが明確になります。

項目 仮想マシン (VM) コンテナ (LXC) コンテナ (Docker)
仮想化対象 OS全体 軽量なOS環境 アプリケーションと実行環境
主な目的 別のOSを動かす 軽量なLinuxサーバー アプリをどこでも同じように動かす
例えるなら 一戸建て マンション 目的特化型のカプセル家電
より具体的な説明
  1. Dockerfile: 環境構築の「手順書」
    Dockerfileは、アプリケーションの実行環境を構築するための手順を順番に記述したテキストファイルです。これは「設計図」や「自動化スクリプト」と考えることができます。
  • ベースの指定 (FROM): 「どのOSを土台にするか」を指定します。(例: FROM ubuntu:22.04、FROM python:3.10-slim)
  • コマンドの実行 (RUN): 必要なソフトウェアやライブラリをインストールします。(例: RUN apt-get update && apt-get install -y nginx)
  • ファイルのコピー (COPY / ADD): 開発したアプリケーションのコード(例: index.html や app.py)を、OSの環境内にコピーします。
  • 起動コマンドの指定 (CMD / ENTRYPOINT): この環境が起動したときに、どのコマンドを実行してアプリケーションを立ち上げるかを定義します。(例: CMD ["nginx", "-g", "daemon off;"])

この「手順書」ファイル(Dockerfile)を docker build コマンドで実行することで、次のステップである「イメージ」が作成されます。

  1. イメージ (Image): 実行環境の「スナップショット」
    イメージは、Dockerfileの手順をすべて実行し、アプリケーションが動く状態を丸ごとパッケージ化したものです。
  • 実体: Dockerfileの各行(FROM, RUN, COPYなど)が実行された結果が、**層(レイヤー)**として積み重なったファイルシステムのスナップショットです。
  • 特徴: 読み取り専用 (Read-only) イメージは一度作成されると変更できません。これが「どこでも同じ環境が動く」ことの保証になります。
  • ITでの例え: オブジェクト指向プログラミングにおける「クラス」に似ています。アプリケーションを実行するための設計図であり、実体ではありません。 あるいは、仮想マシンの「テンプレート」や「ゴールデンイメージ」に近い概念です。

この「イメージ」さえあれば、Dockerが動く環境ならどこでも、次の「コンテナ」を起動できます。

  1. コンテナ (Container): 稼働中の「プロセス」
    コンテナは、**Dockerイメージを実行して、実際にアプリケーションが稼働している状態(実体)**です。
  • 仕組み: 読み取り専用の「イメージ」の上に、書き込み可能なレイヤーを一枚追加して起動します。
  • 動作: OSから見ると、ホストOS上で動く一個の「隔離されたプロセス」のように見えます。コンテナは独自のファイルシステム、ネットワーク、プロセス空間を持ち、他のプロセスやコンテナから分離されています。
  • ITでの例え: イメージが「クラス」なら、コンテナは「インスタンス」です。一つのイメージ(クラス)から、同じ設定のコンテナ(インスタンス)を何個でも素早く起動できます。
  • 揮発性: コンテナ内でファイルを変更したりログを書き込んだりすると、それは「書き込み可能レイヤー」に保存されます。しかし、docker rm でコンテナを破棄すると、その書き込み可能レイヤーも一緒に消えてしまいます(データは消えます)。

Ubuntu ServerへのDocker環境構築

それでは、このDockerの仕組みを、ドメインに参加済みの ubuntu-server-01 (192.168.3.101) にインストールしていきましょう。

Step 1: システムの更新と準備

adm-labuserでSSH接続し、システムを最新化します。Dockerのインストールに必要なツールも併せて導入します。

# パッケージリストの更新とアップグレード
sudo apt update && sudo apt upgrade -y

# 必要なツールをインストール
sudo apt install -y curl wget gnupg lsb-release
AD連携環境でのSudo沼脱出記

実は、上記コマンドの初回実行時、AD連携環境ならではの大きな壁にぶつかりました。sudoを実行すると、adm-labuser is not in the sudoers file.という無情なエラーが表示されたのです。

以前、ADのIT-Administratorsグループにsudo権限を付与したはずなのに、なぜ...?

原因調査の道のり

AD側の確認: まずはドメインコントローラー(ws01)で、adm-labuserがIT-Administratorsグループに所属しているか確認しました。
→ 結果: 問題なく所属。AD側の設定は正しい。

Linux側の認識確認: 次にUbuntu側がグループ所属を正しく認識しているか、idコマンドで確認しました。

id adm-labuser
uid=... gid=... groups=...,1628401111(it-administrators)

→ 結果: なんと、it-administratorsとして正しく認識されている!連携は成功していました。

sudoersファイルの設定確認: 連携が正しいなら、sudoの設定ファイル自体が怪しいと判断。ローカル管理者(labsuser)でログインし直し、sudo visudoで設定を確認しました。

# ...
%IT-Administrators@koikoi_infra.local ALL=(ALL:ALL) ALL

→ 原因判明!ここに2つの問題が潜んでいました。

大文字/小文字: idコマンドの結果はすべて小文字のit-administratorsでしたが、設定ファイルでは大文字始まりのIT-Administratorsになっていました。Linuxは大文字と小文字を厳密に区別します。

ドメイン名の有無: 我が家の環境では、Ubuntuはグループ名をドメイン名なしの短い形式でsudoに渡していました。

解決策

sudoersファイルを、Ubuntuが実際に認識しているグループ名(小文字、ドメイン名なし)に修正しました。

# ...
%it-administrators ALL=(ALL:ALL) ALL

この修正後、adm-labuserで再ログインし、無事にsudoコマンドが実行できるように。異なるOSを連携させる際の、非常に良い教訓となりました。

どのようなツールをインストールしているのか

ブログ記事の「Step 1: システムの更新と準備」でインストールしているツールは、具体的には以下のものです。

sudo apt install -y curl wget gnupg lsb-release

それぞれの主な役割は、記事の「Step 2: Docker公式リポジトリの追加とインストール」のコマンドと密接に関連しています。

  1. curl / wget
    インターネットからファイルをダウンロードするためのツールです。
    記事での使われ方: Step 2でDockerの公式GPGキー(リポジトリが本物であることを証明する電子署名)をダウンロードするためにcurlが使われています。
  2. gnupg (GNU Privacy Guard)
    GPGキーを処理するためのツールです。
    記事での使われ方: Step 2でダウンロードしたGPGキーを、aptが認識できる形式でシステムに登録(gpg --dearmor)するために使われます。これにより、リポジトリからダウンロードするパッケージが「Docker公式によって署名された本物であり、改ざんされていないこと」を検証できます。
  3. lsb-release (Linux Standard Base)
    OSのディストリビューション名やバージョン情報(例: Ubuntu 22.04)を正確に特定するためのツールです。
    記事での使われ方: Step 2でリポジトリを追加する際、$(lsb_release -cs)というコマンドでOSのコードネーム(例: jammy)を取得しています。これにより、お使いのOSバージョンに合った正しいDockerリポジトリを追加できます。

Step 2: Docker公式リポジトリの追加とインストール

気を取り直して、Dockerのインストールを続けます。公式の手順に従い、リポジトリを追加していきます。

# Docker公式GPGキーを追加
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

# Docker公式リポジトリを追加
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Docker Engineをインストール
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

インストール中にサービスの再起動を尋ねる紫色の画面が表示されましたが、これはライブラリが更新されたためで、<Ok>を選択して進めれば問題ありません。

リポジトリとは?apt updateの役割

ここで実行しているコマンドは何をしているのでしょうか?少し寄り道して解説します。

リポジトリとは?
「リポジトリ」とは、ソフトウェアのパッケージ(インストールファイルや設定情報など)を保管している倉庫のようなものです。Ubuntuは、標準で「Ubuntu公式リポジトリ」という巨大な倉庫を持っており、aptコマンドは通常、そこにソフトウェアを探しに行きます。

なぜ新しいリポジトリを追加するの?
Ubuntu公式リポジトリにもDockerはありますが、バージョンが少し古かったりします。常に最新で公式にサポートされたバージョンのDockerを使うために、今回は**「Docker社が公式に運営している専用リポジトリ」**をaptの探し先リストに追加します。

コマンドの役割

  • curl ... | sudo gpg ...: Docker公式リポジトリが本物であることを証明するための「デジタル署名(GPGキー)」をシステムに登録します。これにより、安全なソフトウェアだけをインストールできます。

  • echo "deb [...]" | sudo tee ...: Docker公式リポジトリの「住所(URL)」を、aptの参照リストに書き込んでいます。

  • sudo apt update: これが非常に重要です。このコマンドは、ソフトウェアをインストールするのではなく、aptが持っている「パッケージの品書き(リスト)」を最新の状態に更新する作業です。新しく追加したDocker公式リポジトリにもアクセスし、「うちにはこんなバージョンのDockerがありますよ」という情報をローカルに持ってきてもらいます。

このapt updateを行うことで、初めてaptはDocker社のリポジトリにdocker-ceというパッケージがあることを認識し、次のapt installコマンドで正しくインストールできるようになるのです。

Step 3: 動作確認とsudo不要化設定

最後に、Dockerが正しく動くか確認し、仕上げの設定を行います。

動作確認 (sudoあり)

hello-worldコンテナを実行します。

sudo docker run hello-world

Hello from Docker!というメッセージが表示されればインストール成功です!最初はイメージをネットからダウンロード(pull)するため少し時間がかかります。

もう少し詳しく:コマンドの裏側の流れ

docker run hello-world を実行したとき、内部では以下のことが起きています。

1. コマンドの受付 (クライアント → デーモン)
私たちがターミナルで実行するdockerコマンドは「クライアント」です。この命令は、バックグラウンドで常に動いている「Dockerデーモン(Docker Engine本体)」に送られます。実際にイメージを探したり、コンテナを動かしたりする仕事は、すべてこのデーモンが行います。

2. ローカルイメージの確認
デーモンはまず、「hello-worldという名前のイメージは、すでにこのサーバー(ローカル)にダウンロード済みかな?」と確認します。

3. レジストリへの問い合わせ
ローカルに見つからなかった場合、デーモンは「よし、インターネットから探してこよう」と考えます。ここで、イメージ名 hello-world を解釈します。

hello-world のように、サーバー名/ユーザー名/イメージ名という形式になっていない短い名前の場合、デーモンは設定済みのデフォルトレジストリに問い合わせに行きます。そして、Docker CEをインストールした時点では、このデフォルトレジストリはDocker Hubに設定されています。

Docker Hubとは?:
Docker Hubは、Docker社が公式に提供するコンテナイメージの公開・共有プラットフォームです。GitHubがソースコードを管理するように、Docker Hubはコンテナイメージを管理します。

4. イメージ名の補完
デーモンは、私たちが入力した短い名前を、Docker Hubで通用する完全な名前に内部で補完します。

  • ユーザーが入力: hello-world
  • デーモンが解釈する完全な名前: docker.io/library/hello-world:latest

各部分の意味:

  • docker.io/: デフォルトレジストリであるDocker Hubのサーバー名です。
  • library/: Docker社が公式に管理している優良なイメージが置かれる特別な場所(名前空間)です。これが省略されている場合、デーモンが自動で補ってくれます。
  • :latest: バージョン(タグ)を指定しない場合に、自動で補われる「最新版」を意味するタグです。

5. イメージのダウンロードと実行
デーモンは、この完全な名前を使ってDocker Hubにアクセスし、イメージをダウンロード(pull)してきます。ダウンロードが完了したら、そのイメージを元にコンテナを作成し、実行します。

sudo不要化設定

毎回sudoを打つのは大変なので、adm-labuserをdockerグループに追加します。

# $USERと入力することで現在サインインしているユーザーを指定する
sudo usermod -aG docker $USER

【重要】 この設定を有効にするには、一度ログアウトしてSSHで再接続する必要があります。

コマンドの分解
sudo usermod -aG docker $USER
  1. sudo
    「SuperUser Do(スーパーユーザーとして実行せよ)」の略です。 後に続くコマンドを、システムの管理者権限(root権限)で実行します。ユーザーの所属グループを変更するというシステム設定に関わる操作は、管理者権限が必要なため sudo が必須です。

  2. usermod
    「User Modify(ユーザー情報の変更)」の略です。 既存のユーザーアカウントの設定(どのグループに所属するか、ホームディレクトリはどこかなど)を変更するためのコマンドです。

  3. -aG
    これは usermod コマンドのオプションで、a と G という2つのオプションを組み合わせたものです。
    -G (Groups): ユーザーが所属する「追加グループ」を指定します。
    -a (Append): 「追加する」という意味です。

この -a (Append) が非常に重要です。 もし -a を付けずに sudo usermod -G docker $USER と実行してしまうと、「(他のグループからは全て抜けさせて)docker グループだけに所属させる」という意味になってしまいます。その結果、ユーザーが sudo グループなど重要なグループから外れてしまい、システムにログインできなくなるなどの重大な問題を引き起こす可能性があります。

-aG と組み合わせることで、「現在所属しているグループはそのまま維持しつつ、新しく docker グループを"追加"する」という意味になります。

  1. docker
    追加したいグループの名前です。 Docker Engineをインストールすると、システム上に docker という名前のグループが自動的に作成されます。

  2. $USER
    シェル変数と呼ばれるもので、「現在ログインしているユーザー名」が自動的に入ります。 例えば adm-labuser というユーザーでログインしていれば、システムはこのコマンドを sudo usermod -aG docker adm-labuser として解釈・実行します。

最終確認 (sudoなし)

再ログイン後、今度はsudoなしで実行します。

docker run hello-world

同じようにHello from Docker!と表示されれば、全てのセットアップは完了です!

まとめ

今回の学習を通じて、当初の目標を達成することができました。

特に最後のトラブルシューティングは、単に手順をなぞるだけでは得られない貴重な経験となりました。これでようやくコンテナを動かすための土台が整いました。次回からは、このDocker環境を使って実際にWebサーバーなどのアプリケーションを動かしていきたいと思います!

Discussion