見出し画像

Dockerイメージを1から作成し、Docker Hubへの登録までやってみた

はじめに

こんにちは、SHIFT の開発部門に所属しているKatayamaです。

普段はフロントエンド・バックエンドの開発エンジニアで、アプリケーションの開発をやっていますが、インフラまわりについても学習は進めておきたいと思いました。そこで今回は仮想化技術の 1 つであるコンテナに関して、Docker イメージを自作してローカル環境でそのコンテナを実行し、レジストリに公開する所までをやってみました。

※この記事に書かれている内容は、Linux 環境(Centos7.9)上で実行したもので、あらかじめ Docker がインストール済みである事が前提条件になります。

[root@control-plane docker-kubernetes]# docker version
Client: Docker Engine - Community
 Version:           20.10.7
 API version:       1.41
 ...

Server: Docker Engine - Community
 Engine:
  Version:          20.10.7
  API version:      1.41 (minimum version 1.12)
  ...

※Linux 環境(Centos7.9)への docker のインストール方法はdocker のインストールを参照ください。

Docker イメージを作成する

イメージ作成の基本

Docker イメージを作成する手順としては、普通のプログラム(アプリケーション)の作成手順と何ら変わらず、以下の図のように、① ソースの作成 →② ビルド →③ イメージの完成、という順番になる。

Docker イメージの元 Dockerfile を作成する

Dockerfile とは、Docker 特有のコマンドを利用してイメージの構築手順を定義したもの。よくある Dockerfile の中身の構成例としては以下のようなコードになる。

FROM centos:7

COPY docker-entrypoint.sh /var/tmp

RUN mv /var/tmp/docker-entrypoint.sh /usr/local/bin; \
    chmod +x /usr/local/bin/docker-entrypoint.sh;

ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["echo", "Hello World"]
#!/bin/sh
## docker-entrypoint.sh

env

exec "$@"

以下では上記の Dockerfile の各命令の意味ついてと、命令間の関係性についてみていく。

・参考:Dockerfile reference(日本語サイトはこれ

FROM

イメージを作成するにあたって、ベースとなるイメージを指定する命令。この"FROM"命令は Dockerfile の一番最初に書かなければならないとされている(以下、公式からの引用)。

As such, a valid Dockerfile must start with a FROM instruction. (そのため、正しい Dockerfile は FROM 命令で始まる必要があります。)

今回はCentOSの 7.9 を指定するために Tag"7"を指定している(Docker Hub を見ると分かるが、以下の通り Tag 名"centos7", "7", "centos7.9.2009", "7.9.2009"は全部同じ version の centos になる)。

・参考:FROM(日本語サイトはこれ

COPY

ホスト(Dockerfile が存在する方)のファイルやディレクトリをコピーしてイメージに追加する命令。書き方としては、以下のような形式になり、ホストの SRC をコピーして DEST に追加するという事をしてくれる。

COPY ... 

今回の例で言うと、Dockerfile が存在するディレクトリと同じ階層にある"docker-entrypoint.sh"をコピーして、イメージの"/var/tmp"以下に追加している(イメージに追加するというのは、ベースのイメージとして CentOS7.9 を FROM 命令で指定していたが、その Centos7.9 の"/var/tmp"以下に"docker-entrypoint.sh"を追加するという事)。

・参考:COPY(日本語サイトはこれ

RUN

イメージを作成する際に実行したいコマンドを記述するとそのコマンドを実行する命令。コマンドは複数記述する事が可能。

今回は 2 つの処理をコマンドで実行している。その 2 つとは以下。

① "mv /var/tmp/docker-entrypoint.sh /usr/local/bin;"
"/var/tmp/"以下にある"docker-entrypoint.sh"を、"/usr/local/bin"以下に移     動する
② "chmod +x /usr/local/bin/docker-entrypoint.sh;"
移動後、"docker-entrypoint.sh"のファイルのパーミッションを変更(全て         のユーザーに実行権限を与える)をしている

※個人的には以降で出てくる ENTRYPOINT・CMD と RUN の違いが公式の内容(引用は以下)を読んでもわかりにくかったが、そもそも以下のような違いがあり、本質的に違う。つまり、RUN に記述しているコマンドはイメージの定義に関わるコマンドで、ENTRYPOINT・CMD は実際にコンテナを実行する際の実行時の初期設定に関わるコマンド。(コンテナ実行をする人が変えられる部分は ENTRYPOINT・CMD の部分で、実行する人が変えられない部分が RUN と思えばわかりやすいかも)。

・RUN:"docker build"(イメージ作成)の時に実行される
・ENTRYPOINT・CMD:"docker start/run"(停止中のコンテナの起動/新規コンテナの実行)の時に実行される

The RUN instruction will execute any commands in a new layer on top of the current image and commit the results. The resulting committed image will be used for the next step in the Dockerfile.(RUN 命令は、現在のイメージの上に新しいレイヤーで任意のコマンドを実行し、結果をコミットします。コミットされたイメージは、Dockerfile の次のステップで使用されます。)
Layering RUN instructions and generating commits conforms to the core concepts of Docker where commits are cheap and containers can be created from any point in an image’s history, much like source control.(RUN 命令をレイヤー化し、コミットを生成することは、コミットが安価で、ソース管理のようにイメージの履歴のどの時点からでもコンテナを作成できる Docker の中核概念に適合しています。)

・参考:RUN(日本語サイトはこれ
・参考:chmod コマンド

ENTRYPOINT と CMD

まず、ENTRYPOINT 命令と CMD 命令について、両者が何者か?だが、両者ともコンテナ起動時に実行するコマンドを定義するものである(以下、公式からの引用)。

Both CMD and ENTRYPOINT instructions define what command gets executed when running a container.(CMD と ENTRYPOINT の両命令は、コンテナ実行時に実行されるコマンドを定義します)

ただし、ENTRYPOINT と CMD で動きが違う部分があるのでそれについて詳細を見ていく。また、両方を組み併せて利用した際にはどうなるか?についても見ていく。

CMD

主目的としては、コンテナの実行時のデフォルト処理を設定する命令。この CMD 命令に記載したコマンドは、イメージ実行時("docker run"時)に引数(引数というがコマンドもあり得りそれは以下で見ていく)を渡す事で上書きする事ができる(言い換えると、イメージを実行する時にコンテナに対して何もオプションを指定しなければ、実行時に自動的に実行するコマンド= CMD 命令で指定しているコマンド、という事)。

文字だけだと分かりにくいと思われるので具体的に見ていく。

まず、"docker run -it centos:7"でコンテナを実行してみる。

上記の画像を見て分かるように、コンテナを実行しただけなのに"/bin/bash"がプロセスとして実行されている事が分かる。なんでこうなるのか?だが、これはcentos:7を見て分かるように、centos:7 の Docker イメージには CMD コマンドが定義されており、それがコンテナ実行のデフォルト処理として実行されているから。

CMD ["/bin/bash"]

続いて、以下のようにして"docker run"を実行する際に引数("/bin/sh")を渡してみる。すると今度は/bin/sh が実行される事が確認できる。これが「イメージ実行時に引数を渡す事で上書きする事ができる」といった内容。"bin/sh"と"bin/bash"って何?というのは/bin/sh と /bin/bash の違いなどを参照。

今度は自分で Dockerfile を作成して同じ事をやってみるとどうなるか?を試してみる。以下のような Dockerfile を作成し、"docker build -t test ."でイメージを作成した後、"docker run -it test"でコンテナを実行してみると、一番最初に見た時と同じように"/bin/bash"がプロセスとして実行している事が分かる。これは当たり前で自前で作成した Dockerfile の中身はベースの centos:7 のままなので、centos:7 の CMD 命令のコマンド実行されてこうなっただけ。ちなみに、"docker run -it test /bin/sh"も上記で見た結果と同じになる。

FROM centos:7

では先ほどの Dockerfile に CMD 命令を追記してみるとどうなるか?を見ていく。追記するのは以下のような CMD 命令で、追記後にイメージを作成(ビルド)して実行すると、今度は/bin/sh がプロセスとして実行されている事が分かる。コンテナの実行時のデフォルト処理を記述するのが CMD 命令なのでこのようになった(別の言い方をすると、ベースの centos:7 の CMD 命令は Dockerfile の CMD 命令で上書きされた、という事になる)。

FROM centos:7
CMD ["/bin/sh"]

続いて上記の Dockerfile で作成したイメージを実行する時に"/bin/bash"や"ping -c 3 8.8.8.8"といった引数を渡すとどうなるか?だが、これは引数の内容で上書きされるので以下のようになる。これはコンテナ実行時にその引数で CMD 命令が上書きされたためこうなっている。

まとめると、上記で見てきたように CMD 命令は上書きが繰り返され、実際に実行されるコマンドの優先順位としては以下のようになる。

FROMにしてしているDockerイメージのデフォルトのCMD < DockerfileのCMD < docker run時の引数コマンド

※この CMD だが、主目的の「コンテナの実行時のデフォルト処理を設定する」以外にも、"docker run"時にその引数で上書きされるという性質を利用して、ENTRYPOINT と組み合わせて使用し、ENTRYPOINT の引数に CMD に記載したコマンドを渡すというような使い方をされる事がある。詳細については「ENTRYPOINT と CMD を組み合わせて使う」の章を参照。

・参考:CMD(日本語はここ

ENTRYPOINT

CMD 命令同様に、コンテナの実行時のデフォルト処理を設定する命令(公式だと、直訳で「コンテナーを実行モジュールのようにして実行する設定(An ENTRYPOINT allows you to configure a container that will run as an executable.)」になるが、意訳としては間違ってはいない、と思っている)。

CMD 命令との共通・相違の部分を見ていくと分かりやすいと思うので、実際に Dockerfile を作成してみていく。

同じなのはコンテナ実行時にコマンドを実行するという部分で、以下の画像の通り。

FROM centos:7
ENTRYPOINT ["/bin/sh"]

違う部分としては、コンテナ実行時("docker run"時)に引数を渡すと、その引数が ENTRYPOINT で指定したコマンドの引数になるという事(CMD の場合は上書きだった)。実際にどうなるか?だが、以下の画像のように、"ping -c 3"に続いて"8.8.8.8"が ping の引数渡っている事が分かる(緑の枠で囲っている部分は、ping にホストまたは IP アドレスが指定できていないので表示されているもの)。

FROM centos:7
ENTRYPOINT ["ping", "-c", "3"]

※ただし、コンテナ実行時の引数が ENTRYPOINT 命令のコマンドに渡るのは、exec 形式で記述している時のみなので注意。シェル形式の場合は渡らない(以下、公式からの引用)。

The shell form prevents any CMD or run command line arguments from being used(シェル形式では CMD や run によるコマンドライン引数は受け付けずに処理を行います)

・参考:ENTRYPOINT(日本語はここ

ENTRYPOINT と CMD を組み合わせて使う

ここが少しややこしいと思う所だが、CMD・ENTRYPOINT 共にコンテナ起動時に実行するコマンドを定義するものだとして、両方定義しているとどうなるのか?についてみていく。

両方を定義するパターンで最もベーシックなパターンとしては、ENTRYPOINT のコマンドをコンテナ起動時に実行するコマンドとして定義し、CMD はそのコマンドのデフォルトの引数とする、というパターン。 具体的には、以下のような Dockerfile を作成すると、画像の通り CMD に定義した"8.8.8.8"が ENTRYPOINT のデフォルトの引数として渡っている事が分かる(青色の四角で囲っている部分)。
そして、緑色の四角で囲っている部分では、コンテナ実行時に引数を渡しており、その結果"ping -c 3 1.1.1.1"が実行されている。これは「CMD」の章で見たように CMD 命令はコンテナ実行時の引数で上書きできるのでこのような動きになる。

FROM centos:7
ENTRYPOINT ["ping", "-c", "3"]
CMD ["8.8.8.8"]

上記で見てきたものが ENTRYPOINT・CMD を両方定義する際によく利用されるパターンだが、いくつか注意がある。

・"docker run"コマンドのオプション"--entrypoint"で ENTRYPOINT を上書きする場合、ENTRYPOINT・CMD 共に無視される  

・ENTRYPOINT を exec 形式で定義する場合、CMD も exec 形式で記述しないと意図した引数を渡せない(もしシェル形式で定義すると以下の画像のようにエラーになる。これは何が起きているかというと、以下のような Dockerfile だとコンテナ実行時に"ping -c 3 /bin/sh -c 8.8.8.8"というコマンドを実行しようとするのでエラーになっている。これは公式の表を見る分かる。)

FROM centos:7
ENTRYPOINT ["ping", "-c", "3"]
CMD 8.8.8.8

・「ENTRYPOINT」の章で見たように、ENTRYPOINT をシェル形式で定義すると CMD はデフォルトの引数として使われず、コンテナ実行時の引数が渡る事もない

※ENTRYPOINT・CMD の定義・未定義と、ENTRYPOINT・CMD を exec 形式・シェル形式で書けることを考慮した、ENTRYPOINT・CMD の組み合わせ全パターンの挙動については公式Understand how CMD and ENTRYPOINT interactを参照。

※ENTRYPOINT に CMD が引数として渡る性質を利用して、以下のように CMD のコマンド(コンテナ実行時の引数で渡したコマンド)を ENTRYPOINT の shell 内で"exec"に渡す事で実行するという事もできる。具体的には以下の画像のように、CMD に定義されたコマンドをデフォルトのまま実行したり(緑色の四角で囲っている部分)、コンテナ実行時にその実行する内容を上書きして任意のコマンドを実行したりできる(緑色・オレンジ色の四角で囲っている部分)。

FROM centos:7

COPY docker-entrypoint.sh /var/tmp

RUN mv /var/tmp/docker-entrypoint.sh /usr/local/bin; \
    chmod +x /usr/local/bin/docker-entrypoint.sh;

ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["echo", "Hello World"]

※ちなみに、ENTRYPOINT を設定している時に CMD がベースイメージの方で定義されていた場合、その CMD は無視されるので上記で見てきたような ENTRYPOINT のコマンドの引数として CMD を利用する事はできず、自分で CMD にコマンドを定義する必要がある(以下、公式からの引用)。

If CMD is defined from the base image, setting ENTRYPOINT will reset CMD to an empty value. In this scenario, CMD must be defined in the current image to have a value.(CMD がベースイメージにて定義されていた場合、ENTRYPOINT を設定すると CMD を空の値にリセットします。 そのような場合、CMD へは現イメージにおいて定義を行い、値を持たせておくことが必要です)

・参考:Understand how CMD and ENTRYPOINT interact(日本語はここ


作成した Docker イメージを Docker Hub に push する


この章に書かれてる内容は、あらかじめ Docker Hub のアカウントが必要になるので https://hub.docker.com/signup から登録を済ませている事が前提条件となる。

実際に Docker Hub にイメージを公開していくが、その順序を整理しておくと以下の通りとなる。タグ名追加は必要に応じて行うものになる。

以降では、3 以降の手順について 1 つずつ見ていく。

タグ名追加

これは必要に応じてだが、タグ名を付与する際にはルールがあるので注意。 基本的に、"ユーザー名/イメージ名:タグ"というのが Docker イメージ名の構成だが、ユーザー名・イメージ名(リポジトリ名になる部分)に使える文字列としては英数字(小文字のみ)とセパレーター(ピリオド"."、アンダースコア"_"、ハイフン"-"の 3 つ)のみ。タグ名についてはユーザー名で使える文字に追加で大文字の英字が使える(以下、公式からの引用)。

Name components may contain lowercase letters, digits and separators. A separator is defined as a period, one or two underscores, or one or more dashes. A name component may not start or end with a separator.(名称に含めることができるのは、英小文字と数字とセパレーター文字です。 セパレーター文字は、ピリオド 、1 つか 2 つのアンダースコア、1 つまたは複数のダッシュのいずれかです。 名称の先頭や末尾にセパレーター文字を用いることはできません。)

A tag name must be valid ASCII and may contain lowercase and uppercase letters, digits, underscores, periods and dashes. A tag name may not start with a period or a dash and may contain a maximum of 128 characters.(タグ名にはアスキー文字の中から、英字の大文字小文字、数字、アンダースコア、ピリオド、ダッシュを用います。 タグ名をピリオドやダッシュから始めないようにします。 タグ名の最大文字数は 128 です。)

実際に使える文字を確認してみると以下の画像の通り、ユーザ名(これは Docker Hub の登録時のもの)とイメージ名の部分(repository name になる部分)は小文字のみしか使えないが、タグ名では大文字が使える。

※今回は"docker build"時にあえてタグ名を設定していないが、以下のようなコマンドでビルドすれば"docker tag"の操作は不要

※念のためタグ名を追加した後でもコンテナが実行できるか?を検証してみると、ちゃんと実行できている事が確認できる。

・参考:docker tag(日本語はここ

Docker Hub にログイン

Docker Hub に実際にログインをする。やり方は簡単で"docker login -u [ユーザー名]"とすればいい。パスワードが聞かれるので入力するだけ。

ただし、2 要素認証の設定をしている場合には、パスワードの部分には設定時に発行されたトークンを入力する必要がある。

・参考:docker login(日本語はここ
・参考:Manage access tokens(日本語はここ

プッシュ

これも特に難しい事はなく、プッシュしたいイメージ名を指定して Docker Hub にプッシュするだけ。

実際にプッシュが完了すると、Docker Hub のリポジトリ上でプッシュされたイメージを確認できる(本リポジトリはプライベート設定しているので他の方は Docker Hub 上から見れません)。

・参考:docker push(日本語はここ

まとめとして

今回は実際に Dockerfile を作成して Docker イメージを作成して、Dockerfile の定義の方法や実際にどのような事ができるのか?について理解を深める事ができたと思う。また、Docker Hub に自分で作成したイメージを公開する方法も理解できた。今後は実際にアプリケーションを Docker イメージ化して実行したり、コンテナオーケストレーションとして Kubernetes も触ってみたいと思った。

《この公式ブロガーの記事一覧》


執筆者プロフィール:Katayama Yuta
SaaS ERPパッケージベンダーにて開発を2年経験。 SHIFTでは、GUIテストの自動化やUnitテストの実装などテスト関係の案件に従事したり、DevOpsの一環でCICD導入支援をする案件にも従事。 昨年に開発部門へ異動し、再び開発エンジニアに。座学で読み物を読むより、色々手を動かして試したり学んだりするのが好きなタイプ。

お問合せはお気軽に
https://service.shiftinc.jp/contact/

SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/

SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/

SHIFTの導入事例
https://service.shiftinc.jp/case/

お役立ち資料はこちら
https://service.shiftinc.jp/resources/

SHIFTの採用情報はこちら
https://recruit.shiftinc.jp/career/