kubernetes上にLNMP環境を構築した際のメモ

[前回の記事](https://qiita.com/furuTraMan/items/f482bebf6392e97d1197)にてminikube環境を構築した。

その後、Kubernetesのチュートリアルを一通りこなした後、勉学のためLNMPスタックの構築を実施した際のメモ。

# 実行環境

前回記事と同様のため省略

# 構築する環境外観

こちらの記事([Docker × PHP7.3 × Laravel環境作ってみた](https://qiita.com/A-Kira/items/be0602fe28b445e865db))の環境構築手順を参考とさせていただき、最終的にこんな感じの構成を目指す。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546624/6699d66c-e093-20f3-b02b-58d559024ef7.png" title="lnmp stack" width="80%">

なお、本記事では`Laravel`の構築については触れない。

# 構築するKubernetesオブジェクトについてのおさらい

ここで、構築にて使用するkubernetesオブジェクトについて簡単に説明する。

## Pod

Kubernetesの中で最小のオブジェクト。

アプリケーションを実行する際の項目 (コンテナ、ストレージ、ネットワークIP、実行オプション) をカプセル化したもの。

今回は一つのPodにつき、一つのコンテナイメージが実行されるようにする。

## Node

Podが動作する環境。複数のPodを動作させることが可能。

今回は特に操作しない。

## Namespace

クラスタの仮想的な分割機能。

今回は特に操作せず、`default`を使用。

## Deployment

デプロイするアプリケーション (Pod) について、レプリケーション数、ローリングアップデートなどの制御を実施する。

## Service

実行されているPodへのアクセス経路を提供する。

PodのIPアドレスは一意でないため、アクセスにはService名の指定が必要となる。

また、Podへのアクセス方式にはいくつか種類があり、今回は以下を使用する。

- `ClusterIP`: 外部疎通性のないアクセス方式。クラスタの内部IPを作成する。
- `NodePort`: 外部疎通性のあるアクセス方式。Nodeの指定ポートへのアクセスをコンテナに転送する。

## ConfigMap

アプリケーションの設定データを管理する。

あとで更新が必要などの理由から、コンテナイメージに内包したくない設定については、こちらで持つようにする。

## PersistentVolume (PV)

アプリケーションが使用できる、独立した永続化ストレージ。

## PersistentVolumeClaim (PVC)

アプリケーションがPVを利用する際に経由される。

# 環境の構築作業

以下の手順で構築した。

## nginx環境の構築

まず、PV, PVC, ConfigMapの構築から行う。こちらを行わずDeploymentの構築を実施すると失敗となった。

### ConfigMapの作成

今回、nginx-phpポッド間の通信をservice経由とするので、nginxの`default.conf`をコンテナに内包させずConfigMapとする。

`default.conf`を以下のように記載。この際、phpポッドへのアクセスにservice名と`Port:9000`を指定する。

```
server {
  listen 80;

  root  /var/www/public/;
  index index.php;

  location / {
    try_files $uri $uri/ /index.php$is_args$args;
  }

  location ~ \.php$ {
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    # php serviceに送信する設定にする。
    fastcgi_pass   lnmp-php-service:9000;
    fastcgi_index  index.php;

    include        fastcgi_params;
    fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param  PATH_INFO $fastcgi_path_info;
  }
}
```

```
$ kubectl create configmap nginx-config --from-file={ホスト上のnginx.confの配置されているパス}
```

### PV, PVCの作成

続いてPV, PVCの構築を行う。

今回、nginxポッドがphpアプリケーションを公開するため、ストレージはphpポッドと共有する。

ということでマニュフェストファイルを、`lnmp-php-server-persistentvolumeclaim.yaml`として作成。

```yaml
# PV
kind: PersistentVolume
apiVersion: v1
metadata:
  name: php-server-pv-volume
  labels:
    type: local
spec:
  storageClassName: manual
  capacity:
    # とりあえず100Miくらいの容量で試す
    storage: 100Mi
  # ホストないに作成したストレージへ、Kubernetes nodeがアクセスする際の制約事項
  # 今回は、単一のnodeのみに書き込み/読み込みを可能とする設定とした
  accessModes:
    - ReadWriteOnce
  hostPath:
    # ホストマシンのどこにストレージを割り当てるか
    path: '/root/src/lnmp/server'
---
# PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: php-server-pv-claim
spec:
  storageClassName: manual
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 100Mi
```

コマンドを実行してpv, pvcを作成。

```
$ kubectl apply -f lnmp-php-server-persistentvolumeclaim.yaml
```

以下の`kubectl`コマンドから、作成されていることが確認できた。

```
$ kubectl get pv
NAME                   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                         STORAGECLASS   REASON   AGE
php-server-pv-volume   100Mi      RWO            Retain           Bound    default/php-server-pv-claim   manual                  5d20h
$ kubectl get pvc
NAME                  STATUS   VOLUME                 CAPACITY   ACCESS MODES   STORAGECLASS   AGE
php-server-pv-claim   Bound    php-server-pv-volume   100Mi      RWO            manual         5d20h
```

### deploymentを作成

次に、deploymentを作成する。

マニュフェストファイルを、`lnmp-nginx-deployment.yaml`として作成。

```yaml
# Deployment設定
apiVersion: apps/v1
kind: Deployment
metadata:
  name: lnmp-nginx-deployment
  labels:
    app: lnmp
spec:
  # レプリケーション設定、今回は1台のみの構成とする
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      # 配置するnginxコンテナ
      containers:
      - name: lnmp-nginx-deployment
        image: nginx
        ports:
        - containerPort: 80
        volumeMounts:
        - name: nginx-persistent-storage
          mountPath: /var/www
        # default.conf共有パス
        - name: nginx-config
          # default.confの配置先
          mountPath: /etc/nginx/conf.d
      volumes:
        # nginxの永続化ボリューム
        - name: nginx-persistent-storage
          persistentVolumeClaim:
            claimName: php-server-pv-claim
        # nginx設定のinject
        - name: nginx-config
          configMap:
            name: nginx-config
            items:
            - key: default.conf
              # マウントしたボリュームのmountPath配下に、どういう階層で登録されるかを記載
              path: default.conf
              mode: 0777
```

### serviceを作成

つづいて、serviceを作成する。

マニュフェストファイルを、`lnmp-nginx-service.yaml`として作成。

```yaml
apiVersion: v1
kind: Service
metadata:
  name: lnmp-nginx-service
spec:
  selector:
    app: nginx
  type: NodePort
  ports:
  - port: 80
    targetPort: 80
```

以下コマンドを実行し、デプロイを実施。

```
$ kubectl apply -f lnmp-nginx-deployment.yaml
$ kubectl apply -f lnmp-service-deployment.yaml
```

これでnginxの構築は完了。この時点でnginxサーバーのポートは確認できるけど、`index.php`を配置していないので`502`エラーになる。

## mysql環境の構築

こちらも引き続き構築していく。

### ConfigMapのデプロイ

mysql設定ファイル、`my.cnf`をConfigMapとする。なお、これは元記事のものをそのまま使用する。

```
$ kubectl create configmap mysql-config --from-file={ホスト上のmy.cnfの配置されているパス}
```

### PV, PVCの作成

元記事同様、mysqlのデータディレクトリをマウントするPV, PVCを作成。

```yaml
kind: PersistentVolume
apiVersion: v1
metadata:
  name: mysql-pv-volume
  labels:
    type: local
spec:
  storageClassName: manual
  capacity:
    # とりあえず5Gでマウントする
    storage: 5Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    # プルパスで記載しないとエラーになった
    path: '/root/src/lnmp/mysql/db/data'
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pv-claim
spec:
  storageClassName: manual
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
```

### deploymentを作成

nginxの場合と同様、以下のマニュフェストファイルを作成し、デプロイ。

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: lnmp-mysql-deployment
  labels:
    app: lnmp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: lnmp
  template:
    metadata:
      labels:
        app: lnmp
    spec:
      containers:
      - name: lnmp-mysql-deployment
        image: mysql:5.7
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: root
        - name: MYSQL_DATABASE
          value: test_db
        - name: TZ
          value: 'Asia/Tokyo'
        ports:
        - containerPort: 3306
        args:
        - mysqld
        - --character-set-server=utf8mb4
        - --collation-server=utf8mb4_unicode_ci
        volumeMounts:
        # mysqlストレージ用パス
        - name: mysql-persistent-storage
          mountPath: /var/lib/mysql
        # my.cnf共有パス
        - name: mysql-config
          # my.cnfの配置先
          mountPath: /etc/mysql/conf.d
      volumes:
      # mysqlの永続化ボリューム
      - name: mysql-persistent-storage
        persistentVolumeClaim:
          claimName: mysql-pv-claim
      # mysql設定のinject
      - name: mysql-config
        configMap:
          name: mysql-config
          items:
          - key: my.cnf
            # マウントしたボリュームのmountPath配下に、どういう階層で登録されるかを記載
            path: my.cnf
            mode: 0644
```

### serviceを作成

こちらもnginxの場合と同様のマニュフェストファイルを作成しデプロイする。

ただし、mysqlへの通信はクラスタ内ポッド間でのみ行われるので、`Service.spec.type`を`NodePort`ではなく`ClusterIP`としている。

```yaml
apiVersion: v1
kind: Service
metadata:
  name: lnmp-mysql-service
spec:
  selector:
    app: lnmp
  type: ClusterIP
  ports:
  - port: 3306
    targetPort: 3306
```

## php環境の構築

### deploymentを作成

phpコンテナをデプロイする。

デプロイするphpイメージは元記事の`Dockerfile`から作成する。

コンテナイメージを毎回`dockerhub`から取得しようとしてエラーとなっていたため、コンテナ取得タイミングを`IfNotPresent`とした。

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: lnmp-php-deployment
  labels:
    app: lnmp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: lnmp
  template:
    metadata:
      labels:
        app: lnmp
    spec:
      containers:
      - name: lnmp-php-deployment
        image: laravel
        # なんかdockerhubから取ろうとしてた
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 9000
        volumeMounts:
          - name: php-persistent-storage
            mountPath: /var/www
      volumes:
      # サーバーログ出力先(nginxと共有)
      - name: php-persistent-storage
        persistentVolumeClaim:
          claimName: php-server-pv-claim
```

また、PV, PVCはnginxポッドと共有する。

### serviceを作成

nginxポッドがアクセスするサービスを作成する。

こちらもクラスタ内ポッド間でのみの通信となるため、`Service.spec.type`を`ClusterIP`としている。

```yaml
apiVersion: v1
kind: Service
metadata:
  name: lnmp-php-service
spec:
  selector:
    app: lnmp
  type: ClusterIP
  ports:
  - port: 9000
    protocol: TCP
```

## php環境の動作確認

この段階でLNMP環境にアクセスできることを確認する。

nginxのdefault.confに従い、phpポッドの`/var/www/public/`に、index.phpを配置してみる。

nginx, phpポッドに割り当てた`/root/src/lnmp/server`に以下の内容のindex.phpを配置する。

```php
<h1>kubenetes-php</h1>
<p>Served by Nginx</p>
<?php phpinfo();?>
```

この状態でnginxサーバーにアクセスしてみる。

```
$ kubectl get services/lnmp-nginx-service
NAME                 TYPE       CLUSTER-IP    EXTERNAL-IP   PORT(S)        AGE
lnmp-nginx-service   NodePort   10.98.216.9   <none>        80:32626/TCP   11d```
```

Podのアクセス先が`32626`ポートだと確認できた。

`http://localhost:32626`へブラウザからアクセスする。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546624/879b39d0-7b14-2305-03bc-63d123448514.png" title="php.info">

phpの情報が出た!!

元記事では`Laravel`アプリケーションの構築までを実施していたが、長くなってしまうので一旦ここまで。