centosでvsftpdを起動するDockerコンテナをDockerfileで作成しながらDockerの使い方を覚える

# 概要

DockerコンテナがWindows10 Proで使えるようになっています。
これはつまり、httpやftpを使ったアプリケーションの動作確認が自分のPCでも出来るということです。Docker for WindowsはWindows10のHyper-Vを使ってHyper-V上にDockerコンテナのホストOSを起動し、Hyper-V上のホストOS上でDockerコンテナを動作することができます。Dockerコンテナでwildfly,vsftpdなどをインストールしたcentosやubuntuの起動が出来ます。ここではDockerfileを使用してvsftpが起動するcentosのDockerコンテナイメージをビルドする手順とdocker-composeを使って起動する手順を紹介します。

# Dockerについて
DockerはImmutale Infrastructureや、Infrastrucute as Codeを実現するための強力なツールのようです。
今後、本番環境の更新、本番環境のオーケストレーション、継続的デリバリーなど、これまで手作業で手順書を見ながら人がやっていた作業を、効率良く、しかも高い精度でどんどん出来るようになる予感がします。
また本番環境の更新で神経を使って命を削っていた運用担当者の心理的な負担がかなり減るものと思われます。
その理由は
１．インフラ構築用のコードを事前レビューすることで本番作業に自信をもって臨める。
２．失敗してもすぐに前の状態に戻すことが出来る。
Dockerを使って幸せなエンジニアライフをつかみ取りましょう。

# 前提条件

Dockerはインストール済みとします。
私が使ったDockerのバージョンは次の通りです。
Docker version 18.03.1-ce, build 9ee9f40
Windows 10 Pro(1803) / macOS High Sierra 

なお、Dockerをインストールしてしまえば、macOSでもWindows10でも後の操作方法は同じです。
macOSの場合はターミナルから、Windows 10の場合はコマンドプロンプトを使用してDockerのコマンドで操作します。

# 準備
DockerfileからDockerイメージを作成するための準備をします。
DockerイメージはDockerfileの他に、Dockerコンテナで起動するアプリが使用する設定ファイル類を合わせてビルドするので、これらのファイルを置く専用のディレクトリを作成します。
今後の作業は作成したディレクトリ内で実施します。
ここでは、「vsftp」というディレクトリを作成しました。

「vsftp」ディレクトリ内に、「Dockerfile」「vsftpd.conf」「myapp.sh」の３つのファイルを作成していきます。
最後に、「Docker build」コマンドでDockerコンテナイメージをビルドします。

## なぜDockerfileを使用してDockerコンテナイメージを作成するのか？
手順とは関係ありませんが、なぜDockerfileを使用してDockerコンテナイメージを作成するのでしょうか？Dockerをシンプルに使用するにはDocker Hubからcentosのコンテナイメージを取得して、そのまま起動し、あとは起動したcentos内でvsftpdのインストールや設定などを実施して、vsftpdを使う事も簡単に出来ます。
また、vsftpdのインストールや設定が完了したら、その状態のコンテナイメージを作成し配布することも可能です。つまり例えば開発者にvsftpdのインストール済みの環境を提供する場面があった場合、その配布方法は２通りあります。１つはvsftpdなどが設定済みのDockerコンテナイメージを配布する方法。もう１つはDockerfileを配布して、Dockerコンテナイメージは開発者の環境でビルドして使用して貰う方法。
コンテナイメージを直接配布する方が開発者の負担は少ないように思われますが、次の欠点があります。
### Dockerコンテナイメージ配布の欠点
<ol>
<li>ファイルサイズが大きい。</li>
<li>
イメージの中身が分かりにくい
</li>
</ol>
### Dockerfileで配布の利点
一方Dockerfileで配布の利点は次の通りです。
<ol>
<li>ファイルサイズが小さい。</li>
<li>何をインストールするのかがDockerfileから分かる。</li>
<li>使用するポート番号がDockerfileから分かる。</li>
<li>設定ファイルの変更が容易にできる。</li>
<li>その他、インストールするアプリの追加などの環境変更が容易にできる。</li>
</ol>

以上の理由でDockerイメージを再利用する時にはDockerfileを作成するのが当然の事として考えて頂けるかと思います。
Dockerｲﾒｰｼﾞを作成して使用する場面としては、本番環境で発生したインシデントを、開発者が再現できるようにするために、本番環境のイメージをそのまま保存しておくなどが考えられます。

# 各ファイルの中身

## Dockerfile

「vsftp」フォルダに次の「Dockerfile」を作成して保存します。

```dockerfile

FROM centos
MAINTAINER noriatsu
RUN yum install -y vsftpd
EXPOSE 21
EXPOSE 60000-60100
RUN echo "Hello world." > /var/ftp/pub/test.txt
COPY vsftpd.conf /etc/vsftpd/
COPY myapp.sh  /usr/local/bin/
RUN  chmod 755 /usr/local/bin/myapp.sh
ENTRYPOINT ["/usr/local/bin/myapp.sh"]
```

centsの公式コンテナからイメージを作成します。
vsfpdをインストールしています。
「vsftpd.conf」はvsftpdの設定ファイルです。これをビルド時にコンテナ内にコピーします。
また、ポート２１番と６００００から６０１００までを使用する宣言をしています。
FTPのテスト用に「test.txt」を作成しています。
「myapp.sh」はこの中でvsftpdの起動とシェルの起動を行うためのシェルです。


## myapp.shの作成

「vsftp」フォルダ内に次の「myapp.sh」を作成し、保存します。
なお、改行ｺｰﾄﾞは「LF」とする必要があります。Windows上でﾌｧｲﾙを作成する場合は注意が必要です。


```sh

#!/usr/bin/bash
/usr/sbin/vsftpd /etc/vsftpd/vsftpd.conf
tail -f /dev/null
```

vsfptdを起動しています。
Dockerコンテナは起動時に指定したアプリケーションが終了すると、Dockerコンテナも一緒に終了します。
vsftpdはデーモン起動するため、vsftpdの起動処理が終了するとDockerコンテナも終了します。これを防ぐために、
「tail -f /dev/null」を使ってシェルスクリプトの処理がここで停止する状態を作っています。
デーモンではなく通常のアプリをDockerコンテナで起動する場合は、DockerfileのENTORYPOINTに直接アプリの起動コマンドを書いても問題ありません。

## vsftpd.confの変更

「vsftp」フォルダ内に「vsftpd.conf」を作成し保存します。
ﾃﾞﾌｫﾙﾄのvsftpd.confに下記の3行を追加します。


```conf
pasv_enable=YES
pasv_min_port=60000
pasv_max_port=60100
```
PASVモードに関する設定です。
PASVモードを有効にしてPASVモードで使用するポートを限定しています。
限定している理由は後でこのポートをポートフォワーディングで利用するためです。
FTPは21番ポートを制御用のポイントして使用しますが、データ転送用のポートは任意のポートをFTPサーバーからクライアントに通知し、クライアントから指定されたFTPサーバーのポートに接続します。（PASV通信の場合です。）
DockerコンテナはDockerのホストとNATスイッチで接続していますので、NATスイッチでDockerコンテナ内で使用するポートとホストの待ち受けポートの転送の設定が必要になります。この転送設定のポート番号を限定するためにPASVポートの接続範囲を決めています。
任意のポートで問題ありませんが、Dockerのホスト側で未使用のポート範囲を割り当てる必要があります。

ﾌｧｲﾙ全文はこちらです。

<details>
<summary>vsftpd.conf</summary>
<div>


```conf

# Example config file /etc/vsftpd/vsftpd.conf
#
# The default compiled in settings are fairly paranoid. This sample file
# loosens things up a bit, to make the ftp daemon more usable.
# Please see vsftpd.conf.5 for all compiled in defaults.
#
# READ THIS: This example file is NOT an exhaustive list of vsftpd options.
# Please read the vsftpd.conf.5 manual page to get a full idea of vsftpd's
# capabilities.
#
# Allow anonymous FTP? (Beware - allowed by default if you comment this out).
anonymous_enable=YES
#
# Uncomment this to allow local users to log in.
# When SELinux is enforcing check for SE bool ftp_home_dir
local_enable=YES
#
# Uncomment this to enable any form of FTP write command.
write_enable=YES
#
# Default umask for local users is 077. You may wish to change this to 022,
# if your users expect that (022 is used by most other ftpd's)
local_umask=022
#
# Uncomment this to allow the anonymous FTP user to upload files. This only
# has an effect if the above global write enable is activated. Also, you will
# obviously need to create a directory writable by the FTP user.
# When SELinux is enforcing check for SE bool allow_ftpd_anon_write, allow_ftpd_full_access
#anon_upload_enable=YES
#
# Uncomment this if you want the anonymous FTP user to be able to create
# new directories.
#anon_mkdir_write_enable=YES
#
# Activate directory messages - messages given to remote users when they
# go into a certain directory.
dirmessage_enable=YES
#
# Activate logging of uploads/downloads.
xferlog_enable=YES
#
# Make sure PORT transfer connections originate from port 20 (ftp-data).
connect_from_port_20=YES
#
# If you want, you can arrange for uploaded anonymous files to be owned by
# a different user. Note! Using "root" for uploaded files is not
# recommended!
#chown_uploads=YES
#chown_username=whoever
#
# You may override where the log file goes if you like. The default is shown
# below.
#xferlog_file=/var/log/xferlog
#
# If you want, you can have your log file in standard ftpd xferlog format.
# Note that the default log file location is /var/log/xferlog in this case.
xferlog_std_format=YES
#
# You may change the default value for timing out an idle session.
#idle_session_timeout=600
#
# You may change the default value for timing out a data connection.
#data_connection_timeout=120
#
# It is recommended that you define on your system a unique user which the
# ftp server can use as a totally isolated and unprivileged user.
#nopriv_user=ftpsecure
#
# Enable this and the server will recognise asynchronous ABOR requests. Not
# recommended for security (the code is non-trivial). Not enabling it,
# however, may confuse older FTP clients.
#async_abor_enable=YES
#
# By default the server will pretend to allow ASCII mode but in fact ignore
# the request. Turn on the below options to have the server actually do ASCII
# mangling on files when in ASCII mode.
# Beware that on some FTP servers, ASCII support allows a denial of service
# attack (DoS) via the command "SIZE /big/file" in ASCII mode. vsftpd
# predicted this attack and has always been safe, reporting the size of the
# raw file.
# ASCII mangling is a horrible feature of the protocol.
#ascii_upload_enable=YES
#ascii_download_enable=YES
#
# You may fully customise the login banner string:
#ftpd_banner=Welcome to blah FTP service.
#
# You may specify a file of disallowed anonymous e-mail addresses. Apparently
# useful for combatting certain DoS attacks.
#deny_email_enable=YES
# (default follows)
#banned_email_file=/etc/vsftpd/banned_emails
#
# You may specify an explicit list of local users to chroot() to their home
# directory. If chroot_local_user is YES, then this list becomes a list of
# users to NOT chroot().
# (Warning! chroot'ing can be very dangerous. If using chroot, make sure that
# the user does not have write access to the top level directory within the
# chroot)
#chroot_local_user=YES
#chroot_list_enable=YES
# (default follows)
#chroot_list_file=/etc/vsftpd/chroot_list
#
# You may activate the "-R" option to the builtin ls. This is disabled by
# default to avoid remote users being able to cause excessive I/O on large
# sites. However, some broken FTP clients such as "ncftp" and "mirror" assume
# the presence of the "-R" option, so there is a strong case for enabling it.
#ls_recurse_enable=YES
#
# When "listen" directive is enabled, vsftpd runs in standalone mode and
# listens on IPv4 sockets. This directive cannot be used in conjunction
# with the listen_ipv6 directive.
listen=NO
#
# This directive enables listening on IPv6 sockets. By default, listening
# on the IPv6 "any" address (::) will accept connections from both IPv6
# and IPv4 clients. It is not necessary to listen on *both* IPv4 and IPv6
# sockets. If you want that (perhaps because you want to listen on specific
# addresses) then you must run two copies of vsftpd with two configuration
# files.
# Make sure, that one of the listen options is commented !!
listen_ipv6=YES

pam_service_name=vsftpd
userlist_enable=YES
tcp_wrappers=YES

pasv_enable=YES
pasv_min_port=60000
pasv_max_port=60100

```

</div>
</details>

※このvsftpd.confファイルも改行コードは「LF」で保存する必要があります。

# Dockerファイルをビルドします。

「Dockerfile」「vsftpd.con」「myapp.sh」の３のファイルの準備ができたところで、いよいよDockerイメージをビルドします。

「vsftp」フォルダで次のコマンドでDockerイメージをビルドします。

```sh

>docker build -t yourname/vsftp .
```

「yourname/vsftp」はイメージに付ける名前のため適当に修正する必要があります。（このままでも支障はありません。）
最後の「.」はカレントディレクトのファイルをビルド対象とするために必要です。

なお、できたイメージは次のコマンドで中身の確認ができます。

```
>docker inspect yourname/vsftp
```
JSON形式でDockerイメージの内容が表示されます。


# Dockerｲﾒｰｼﾞをdocker-composeで起動します。

## docker-composeﾌｧｲﾙの準備

dockerｲﾒｰｼﾞ作成用のﾌｫﾙﾀﾞｰ「vsftp」の１つ上の階層など、任意のﾌｫﾙﾀﾞｰで
「docker-compose.yml」ﾌｧｲﾙを次の通り作成します。
このﾌｧｲﾙを使ってDockerｲﾒｰｼﾞからDockerｺﾝﾃﾅを起動します。

```yaml:docker-compose.yml
version: '3'
services:
 ftp:
  image: yourname/vsftp
  ports:
    - "21:21"
    - "60000-60100:60000-60100"
  privileged: true

```

「yourname/vsftp」の部分はDockerｲﾒｰｼﾞﾋﾞﾙﾄﾞ時に実際に設定した名前に置換が必要です。
なお、yamlﾌｧｲﾙなので、ﾀﾌﾞ文字ではなく空白文字を使ってｲﾝﾃﾞﾝﾄする必要があるので注意が必要です。
また、コロン（:）の後にも空白文字が必要です。
portsはポートフォワーディングの設定です。
Dockerコンテナ内で使用するポートとホスト側のポートを同じ番号で転送設定しています。
--privilegedはデーモン起動するための特権設定です。
この指定がないとvsftpdの起動に失敗します。

## いざDockerｺﾝﾃﾅを起動

```
>docker-compose up -d
Recreating dockerfiles_ftp_1 ... done
```

vsftpdが起動しているcentosのｺﾝﾃﾅが起動します。
「-d」はデタッチオプションです。このオプションにより、Dockerコンテナ起動後に、
元のプロンプトに戻ります。Dockerコンテナ自体は起動したままの状態で、元のプロンプトに戻ります。
Dockerプロセスが起動していることを確認するには、

```
>docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                                      NAMES
60abdd982e7d        yourname/vsftp             "/usr/local/bin/myap…"   7 seconds ago       Up 3 seconds        0.0.0.0:21->21/tcp, 0.0.0.0:60000-60100->60000-60100/tcp   vsftp1_myftp1_1
```

WinScpなどで「localhost」にユーザー名「anonymous」、パスワードは適当なメールアドレスで
FTP接続すると、pubﾌｫﾙﾀﾞｰに「test.txt」ﾌｧｲﾙが見えるようになっているはずです。

docker-composeを使用しなくても、次のDockerコマンドにより同じように起動ができますが、
docker-composeを使った方が確実にｵﾌﾟｼｮﾝなどを間違わずに起動できるかと思います。

```
>docker run --privileged -p 21:21 -p 60000-60100:60000-60100 -it -d yourname/vsftp 
```

# Dockerコンテナの停止と後片付け

起動に使ったdocker-compose.ymlがあるディレクトリをカレントディレクトとして、

```
>docker-compose down
```

でDockerコンテナを停止させることができます。
なお、停止したDockerコンテナは停止状態のDockerプロセスとして残っています。

```
>docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                        PORTS                                                      NAMES
60abdd982e7d        yourname/vsftp             "/usr/local/bin/myap…"   2 minutes ago       Up 2 minutes                  
```

でDockerプロセスとして残っていることが確認できます。
これを削除するには、コンテナID（上位４桁でも良い）を指定して「docker rm」コマンドで削除します。

```
>docker rm 60ab
```

コンテナIDは環境によって異なるので、確認して入力が必要です。

この状態でも、Dockerイメージとしては残っています。

```
>docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
yourname/vsftp             latest              c4063118ef95        34 minutes ago      269MB
```

Dockerイメージは次のように「docker rmi」コマンドで削除が可能です。
ここでは、イメージID(上位４桁でも良い）を指定して削除します。

```
>docker rmi c406
```

イメージIDは環境によって異なるので確認して入力が必要です。

## トラブルシューティング

下記の理由でvsftpdが正常起動しない場合があります。

１）myapp.shやvsftpd.confファイルの改行コードが「CR + LF」になっている。
 →改行コードを「LF」に変更して保存しましょう。
２）FTPサーバーのPASVポートがすでに別のサービスによって使用されている。
 →PASVポートの範囲を他のサービスで使用されていないポートにしましょう。
Windowsの場合は「>netsta -an」コマンドで現在他のサービスで使用中のポートは「LISTENING」状態になっています。