nginx のコンテナを Docker や minikube(kubernetes) で動かし、静的コンテンツ公開工程を模倣

# はじめに

記載情報は CI/CD に関連します。  
実務上関わった環境・機能を身近な情報としてお届けしたく、投稿をはじめました。  
各学習上、お金のかからない方法を選択していきます。

以下の手順を実施することで、運用者が nginx の Docker Image を利用して、静的コンテンツを公開する工程を模倣します。

## 対象者と関連環境・機能

この記事は下記のような人を対象にしています。

- CI/CD 初学者
- プログラミング初学者
- 駆け出しエンジニア

記載している環境・機能は以下です。

- nginx
- nginx.conf
- default.conf.template
- Docker
- minikube(kubernetes)

## まえおき

お仕事で nginx のコンテナ使って静的コンテンツ公開する部位の設計を命じられました。  
で、恥ずかしながら nginx の読み方から調べました。（エンジンエックス、ですかね。）  
もう設計始まるので時間もなくどこから手を付けてよいやら悩みましたが、直近調べたことをまずは残そうとしています。

元々、Web サーバーとして Apache は聞いたことがありましたが、これは色々な用途で活用できる有名どころとして耳に入っていたのかもしれません。  
今回取り扱うものが「静的コンテンツ」であることより、「nginx」の選定となった模様でした。  
（ザックリ印象「静的コンテンツの表示」や「リバースプロキシ」、「ロードバランサー」といった動作を得意とした Web サーバでしょうか。）

あと、OpenShift とか kubernetes で他所のボリューム見に行くところも初。  
いや、手元の Docker でマウントとかもそういえばしたことなくて、自習に至った形です。

## 目論見

以下の流れで進めようと思いました。

- nginx のコンテナを動かす
- nginx のコンテナを Dockerfile で作って動かす
- nginx の利用上で必要になるであろう、port 番号やルートパスの指定方法を抑えておく
- 作った nginx イメージに Docker 上でマウントしてみる
- Docker でマウントする方法と、kubernetes でマウントする方法でどんな具合の差になるのかを抑えておく

恐らく、、たぶん、ここまで把握できれば、あとは何等か環境が変わっても部分的な書き換えとかでなんとか行けるのではないか。。

## 前提作業

- ubuntu に Docker をインストールする　[Install Docker Engine on Ubuntu](https://docs.docker.com/engine/install/ubuntu/)
- minikube、kuectl のインストール
  - [minikube Get Started!](https://minikube.sigs.k8s.io/docs/start/)
  - [Linux へ kubectl をインストールする](https://kubernetes.io/ja/docs/tasks/tools/install-kubectl/#install-kubectl-on-linux)

## nginx のコンテナを動かす

真っ先に参照したのは Docker Hub 上の nginx 本家イメージ。とにかく最短動かしたく。  
[nginx - Official Image - Docker Hub](https://hub.docker.com/_/nginx)

```nginx:latest
$ docker pull nginx:latest　　※latestをプル
latest: Pulling from library/nginx
Digest: sha256:943c25b4b66b332184d5ba6bb18234273551593016c0e0ae906bab111548239f
Status: Image is up to date for nginx:latest
docker.io/library/nginx:latest
$ docker run -d -p 8080:80 nginx:latest　　※起動
513779d0aaa6b8e3486a4809fa4aa1bd769a7bf78993255007ccf096c5f7238f
$ curl http://localhost:8080/　　※接続OK
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
```

## nginx のコンテナを Dockerfile で作って動かす

ここも本家の情報で行けました。

```Dockerfile
FROM nginx
COPY nginx.conf /etc/nginx/nginx.conf
```

で、nginx.conf とは設定ファイルなのではないか。そうだとありがたく。  
先ほど立ち上げた nginx で見てみると以下。  
あれ、port 番号やルートパスの指定するのってこれではないの？

```nginx.conf
$ docker exec -it 0a3df5313506 bash
root@0a3df5313506:/# cat /etc/nginx/nginx.conf

user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;　　※ここが怪しい
}
```

## nginx で必要になるであろう、port 番号やルートパスの指定方法を抑えておく

本家サイトに記載はありました。。分かる人なら分かるかもしれませんが、私にはちょっと。。

```
So if you place templates/default.conf.template file, which contains variable references like this:

listen       ${NGINX_PORT};
outputs to /etc/nginx/conf.d/default.conf like this:

listen       80;
```

default.conf.template があるかというとそのようなものはありません。

```
root@0a3df5313506:/etc/nginx# pwd
/etc/nginx
root@0a3df5313506:/etc/nginx# ls -la
total 48
drwxr-xr-x 1 root root 4096 Oct 25 10:23 .
drwxr-xr-x 1 root root 4096 Nov 13 03:05 ..
drwxr-xr-x 1 root root 4096 Nov 13 03:05 conf.d
-rw-r--r-- 1 root root 1007 Oct 19 07:56 fastcgi_params
-rw-r--r-- 1 root root 5349 Oct 19 07:56 mime.types
lrwxrwxrwx 1 root root   22 Oct 19 09:32 modules -> /usr/lib/nginx/modules
-rw-r--r-- 1 root root  648 Oct 19 09:32 nginx.conf
-rw-r--r-- 1 root root  636 Oct 19 07:56 scgi_params
-rw-r--r-- 1 root root  664 Oct 19 07:56 uwsgi_params
```

ただ、conf.d/default.conf があり、nginx.conf 定義の「include /etc/nginx/conf.d/\*.conf;　※ここが怪しい」で読んでいるような。  
「default.conf.template は default.conf を元に注入したい環境変数を変数化して己で配置」が妥当なような。  
実際やってみると意図通りにはなりました。

```Dockerfile
FROM nginx
COPY static-html-directory /usr/share/nginx/html　　　　※静的コンテンツを配置
COPY nginx.conf /etc/nginx/nginx.conf　　　　　　　　　　※ここは変更加えていませんが、後で外からも注入できるように一応コピー
COPY default.conf.template /etc/nginx/templates/default.conf.template　※default.conf を元に作ったテンプレートを配置
```

```default.conf.template
server {
    listen       ${NGINX_PORT};　　※ここにコンテナ起動時に注入する環境変数が入る想定
    listen  [::]:80;
    server_name  localhost;

    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        root   /app;
省略
```

```
$ docker build -t oad3jp999/my-nginx:1.0.0 .
Sending build context to Docker daemon  14.85kB
Step 1/4 : FROM nginx
 ---> 76c69feac34e
Step 2/4 : COPY static-html-directory /usr/share/nginx/html
 ---> Using cache
 ---> 00de7b966f5c
Step 3/4 : COPY nginx.conf /etc/nginx/nginx.conf
 ---> Using cache
 ---> c0a6076cded2
Step 4/4 : COPY default.conf.template /etc/nginx/templates/default.conf.template
 ---> 35f5ff0e0e9e
Successfully built 35f5ff0e0e9e
Successfully tagged oad3jp999/my-nginx:1.0.0
$ docker run -d \
>   -it \
>   -p 8080:81 \　　　　　　※コンテナの81番ポートに、外より8080でつながる
>   --name my-nginx \
>   --env NGINX_PORT=81 \　※コンテナ起動時に注入するポート番号
>   oad3jp999/my-nginx:1.0.0

b4d4e29956b8fa146e6bbfc9e2d02dfd455d506e8379aa0bcdb7f36813d871a6
$
$ curl http://localhost:8080/　　※接続OK
hello this is static-text from docker coppy
```

## 作った nginx イメージに Docker 上でマウントしてみる

テンプレートをちょっと変えてみて。

```default.conf.template
server {
    listen       ${NGINX_PORT};
    listen  [::]:80;
    server_name  localhost;

    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        root   /app;　※コンテナ内の何も入っていない/appにマウントしていきます
        # root   /usr/share/nginx/html;
        index  send.txt;　※/appに置いたsend.txtがindexとして読みだされる想定
        # index  index.html index.htm send.txt;
    }
```

docker build してテンプレートを再配置

```docker build
$ docker build -t oad3jp999/my-nginx:1.0.0 .
Sending build context to Docker daemon  13.82kB
Step 1/4 : FROM nginx
 ---> 76c69feac34e
Step 2/4 : COPY static-html-directory /usr/share/nginx/html
 ---> Using cache
 ---> 00de7b966f5c
Step 3/4 : COPY nginx.conf /etc/nginx/nginx.conf
 ---> Using cache
 ---> c0a6076cded2
Step 4/4 : COPY default.conf.template /etc/nginx/templates/default.conf.template
 ---> 39f088b173e2
Successfully built 39f088b173e2
Successfully tagged oad3jp999/my-nginx:1.0.0
```

```docker run
$ docker run -d \
>   -it \
>   -p 8080:81 \
>   --name my-nginx \
>   --mount type=bind,source="$(pwd)"/target,target=/app,readonly \　　※ここで、ホストマシン側の「"$(pwd)"/target」を、コンテナ内の「/app」へマウント
>   --env NGINX_PORT=81 \
>   oad3jp999/my-nginx:1.0.0
cc97265d50624a1d5455665b83c1af58f288ca067e07c454269b7ca659c2a0c1
$ curl http://localhost:8080/　　※接続OK
hello this is mount target with nginx!
add text
$ docker exec -it my-nginx bash
root@cc97265d5062:/# cat /app/send.txt　　※ディレクトリがマウントされて、send.txtなるファイルがルートアクセスで表示。ホスト側で「add text」を追記したらコンテナ側でも認識OK
hello this is mount target with nginx!
add text
root@cc97265d5062:/#
```

## Docker でマウントする方法と、kubernetes でマウントする方法でどんな具合の差になるのかを抑えておく

結論、コンテナ起動時にマウント指定する点で類似。  
（Docker の思想として環境依存なしに動くことが重要視されている点からは、なるほど、コンテナ起動時の指定がよさそうですね。納得。）  
あとは接続可能なストレージは複数存在するも、「volumes」の書きっぷりを OpenShift 側の PVC なるものと比較しながら修正すれば、なんとなく行けそうな。  
ということで minikube で 準備。

```
$ minikube start --driver=docker　　※minikube起動
* Ubuntu 20.04 上の minikube v1.22.0
* プロフィールを元に、 docker ドライバを使用します
* コントロールプレーンのノード minikube を minikube 上で起動しています
* イメージを Pull しています...
* 既存の docker container を "minikube" のために再起動しています...
* Docker 20.10.7 で Kubernetes v1.21.2 を準備しています...
* Kubernetes コンポーネントを検証しています...
  - イメージ kubernetesui/metrics-scraper:v1.0.4 を使用しています
  - イメージ kubernetesui/dashboard:v2.1.0 を使用しています
  - イメージ gcr.io/k8s-minikube/storage-provisioner:v5 を使用しています
* 有効なアドオン: storage-provisioner, default-storageclass, dashboard

! /usr/local/bin/kubectl is version 1.23.5, which may have incompatibilites with Kubernetes 1.21.2.
  - Want kubectl v1.21.2? Try 'minikube kubectl -- get pods -A'
* 完了しました！ kubectl が「"minikube"」クラスタと「"default"」ネームスペースを使用するよう構成されました

$ cd /tmp
$ mkdir hostpath_pv　　　※マウント用ディレクトり作成
$ ls -la
合計 96
drwxrwxrwt 21 root  root  4096 11月 13 12:54 .
drwxr-xr-x 21 root  root  4096  3月 14  2021 ..
drwxrwxr-x  2 senju senju 4096 11月 13 12:54 hostpath_pv
$ cd hostpath_pv
$ vi send.txt
$ cat send.txt
This is mount from hostpath for minikube!　　　※マウントされた際には、nginxがこちらを返す想定
$ cd ..
$ minikube mount /tmp/hostpath_pv:/host　　　※前段として、minikubeのVMが認識するようにマウントしておく必要がある模様
* Mounting host path /tmp/hostpath_pv into VM as /host ...
  - マウントタイプ:
  - ユーザー ID:      docker
  - グループ ID:     docker
  - バージョン:      9p2000.L
  - メッセージのサイズ: 262144
  - Permissions:  755 (-rwxr-xr-x)
  - Options:      map[]
  - アドレスをバインドします: 192.168.58.1:40645
* Userspace file server: ufs starting
* Successfully mounted /tmp/hostpath_pv to /host　　※ホストマシンの「/tmp/hostpath_pv」が「/host」としてマウントされた

* NOTE: This process must stay alive for the mount to be accessible ...

```

Pod を浮かべる用の yml を準備

```my-nginx.yml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
    - image: oad3jp999/my-nginx:1.0.0
      name: nginx
      env:
      - name: NGINX_PORT　　※テンプレートで変数化したポート番号を環境変数として注入
        value: "81"
      volumeMounts:
        - name: storage
          mountPath: /app　　※nginxの「/app」にマウントする。⇒ルートディレクトリに指定してあるので、例の「send.txt」なるものが読まれる。
  volumes:
    - name: storage
      hostPath:　　　※本来、kubernetesではホストパスのストレージマウントは極めて限定的な目的での利用とされているが、いまはymlの書きっぷりが把握したいので、うってつけ
        path: /host　※ホストマシンの「/tmp/hostpath_pv」がマウントされた「/host」を指定
        type: DirectoryOrCreate　※直アクセス、無ければ作る
```

Pod を浮かべて接続 OK

```apply
$ kubectl apply -f my-nginx.yml
pod/nginx created
$ kubectl get all -o wide
NAME        READY   STATUS    RESTARTS   AGE   IP           NODE       NOMINATED NODE   READINESS GATES
pod/nginx   1/1     Running   0          7s    172.17.0.5   minikube   <none>           <none>

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE   SELECTOR
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   28h   <none>
$ kubectl exec -it nginx bash　　※面倒なので、中から突きます
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
root@nginx:/# curl http://localhost:81/　　※接続OK
This is mount from hostpath for minikube!　　※想定通り！
root@nginx:/#
```

## 関連記事

- [CI/CD とは?](https://qiita.com/wf-kenji-ikeda/items/bfb6d5e21b33d0cca83b)
- [Azure Devops (Artifacts) を無償、無料で使う](https://qiita.com/wf-kenji-ikeda/items/9c21859d8b5b6a63246e)
- [Spring Tool Suite 4 インストール、Spring Initializr 、gradlew の利用](https://qiita.com/wf-kenji-ikeda/items/d00766da8a4076ec43ed)
- [Spring Boot Web アプリケーションを作る](https://qiita.com/wf-kenji-ikeda/items/1e64e44094ce8a1d997f)
- [Jar の Library を作り、Azure Artifacts を利用して成果物を配布する](https://qiita.com/wf-kenji-ikeda/items/696bda335a8933abfcb3)
- [ライブラリ開発者より指定されたバージョンを利用してアプリを作る](https://qiita.com/wf-kenji-ikeda/items/5bf06ce19c52e73a5463)
- [アプリを Docker 、Docker Compose でコンテナを動かしてみる](https://qiita.com/wf-kenji-ikeda/items/e6604ea6ae257f3925a7)
- [Azure Devops (PileLines) を無償、無料で使う](https://qiita.com/wf-kenji-ikeda/items/451838da3b4ac45a8d65)
- [Azure Devops (PileLines) でテスト、ビルド、イメージプッシュをする](https://qiita.com/wf-kenji-ikeda/items/5ddf6158bb98c4174db9)
- [minikube(kubernetes) でコンテナを動かしてみる](https://qiita.com/wf-kenji-ikeda/items/95604d83850a17b91c46)
- [nginx のコンテナを Docker や minikube(kubernetes) で動かし、静的コンテンツ公開工程を模倣](https://qiita.com/wf-kenji-ikeda/items/bf6cb1b7a047f562d309)