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

[前回記事](https://qiita.com/furuTra/items/7002f4d2c3f8a833d842)の続きとなる。

kubernetes上に作成したLNMP環境に対してLaravelプロジェクトをデプロイした際のメモ。

# Laravelプロジェクトの構築

今回、構築作業にヒューマンオペレーションが入り込むのを極力避けることを目的とし、Laravelプロジェクト作成をコンテナログインからのコマンド実行以外で実施することとした。

## Laravelプロジェクトの初期化

Pod作成時に`command`から実施してしまうと、Pod起動の度にプロジェクトの作成がされてしまう。

ということで、Laravelプロジェクトの初期化限定で、[Jobオブジェクト](https://kubernetes.io/ja/docs/concepts/workloads/controllers/job/)を利用することにした。

### Jobの作成

`Job`は、コンテナを利用し処理を実行するリソースである。

Jobにて作成されたコンテナは、マニフェストファイルに記載されているタスク完了後、削除される。

今回は1回限りのタスクを実行するので、`completions(成功回数):1/parallelism(並列度):1/backoffLimit(失敗許容回数):0`とする。

また、今回、`spec.ttlSecondsAfterFinished: 30`とし、Laravelプロジェクト作成後、30sでPodを削除するようにする。

これを利用し、phpポッドの利用するPVに対してLaravelプロジェクトのデプロイのみ行う。

```yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: laravel-init-job
  labels:
    app: lnmp
spec:
  # One Shot Task
  completions: 1
  parallelism: 1
  backoffLimit: 0
  # 実行後30sでJob実行Podを削除
  ttlSecondsAfterFinished: 30
  template:
    metadata:
      labels:
        app: lnmp
    spec:
      containers:
      - name: laravel-init
        image: laravel
        imagePullPolicy: IfNotPresent
        # laravelプロジェクトの初期構築
        command:
          - sh
          - -c
        args:
          - "composer create-project --prefer-dist laravel/laravel '' \
            && chmod 777 -R ./storage/ \
            && chmod 777 -R ./bootstrap/"
        volumeMounts:
          - name: php-persistent-storage
            mountPath: /var/www
      restartPolicy: Never
      volumes:
      - name: php-persistent-storage
        persistentVolumeClaim:
          claimName: php-server-pv-claim
```

上記`Job`マニフェストファイル`laravel-init-job.yaml`をデプロイ。

```
$ kubectl apply -f laravel-init-job.yaml
```

### Job実行時の不具合

Job実行時に以下のエラーとなった。

```
Creating a "laravel/laravel" project at "./"
Installing laravel/laravel (v8.1.0)
As there is no 'unzip' command installed zip files are being unpacked using the PHP zip extension.
This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost.
Installing 'unzip' may remediate them.
  - Installing laravel/laravel (v8.1.0): Downloading (100%)
Created project in /var/www/public
> @php -r "file_exists('.env') || copy('.env.example', '.env');"
Loading composer repositories with package information
Updating dependencies (including require-dev)
Killed
```

なんか`killed`となり、インストールに失敗している。当然Laravelプロジェクトは作成されていない。

こちらの記事([[PHP] composer install が killed で失敗するときの原因と対処](https://webbibouroku.com/Blog/Article/composer-killed))にドンピシャで答えが載っていた。

どうやらメモリが足らないのが原因だったらしい。今回、phpのデプロイメントにはメモリ制限は設けていないので、ホストのメモリがそもそも足りていないと思われる。

ということでminikubeサーバーのメモリを`2.5G`から`4.0G`へ増やし、マウントしていたボリューム`php-persistent-storage`内のファイルを削除。

再びJobを実行することでLaravelの作成に成功した。

### Jobを実行したPodが削除されない

`spec.ttlSecondsAfterFinished: 30`としたにも関わらず、Jobを実行したPodが`status: terminating`のまま残り続けた。

調べてみると、こちらの記事([Kubernetes で Complete/Failed となった Job を自動削除する](https://qiita.com/ryysud/items/76b6bcf12f0678dc24c4))にて説明がされていた。

どうやら`TTLAfterFinished	`は、[フィーチャーゲート](https://kubernetes.io/ja/docs/reference/command-line-tools-reference/feature-gates/#feature-gates-for-alpha-or-beta-features)なる実験的機能であるらしい。

ということで、以下コマンドから`minikube`を起動し、機能を有効化する。(kubernetes > v1.12以上である必要がある。)

```
$ minikube start --feature-gates=TTLAfterFinished=true
```

この状態でJobを実行することで、Podがコマンド終了後に削除されるようになった。

## Laravel環境変数ファイル(.env)をConfigMap化

mysql-phpポッド間の通信をサービス経由で実行するにあたり、Laravelの`.env`ファイルにサービス名を指定する必要がある。

そこで、`.env`を`ConfigMap`とし、コンテナ外部から設定できるようにする。

### .envの修正

こちらの記事([docker-composeでLaravelの開発環境を整える方法とその解説](https://www.membersedge.co.jp/blog/laravel-development-environment-with-docker-compose/))を参考にし、`.env`の以下箇所を修正

```
DB_CONNECTION=mysql
# mysql-php間の通信で利用するService名
DB_HOST=lnmp-mysql-service
DB_PORT=3306
DB_DATABASE=test_db
DB_USERNAME=user
DB_PASSWORD=password
```

### ConfigMapの作成

以下コマンドを実行し、ConfigMap `laravel-env`を作成する。

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

### ConfigMapをマウント

phpのDeploymentに作成したConfigMapをマウントし、再度読み込む。

この際、以前作成したボリュームと、ConfigMapのマウントパスが被ってしまいエラーとなった。

そこで、こちらの記事（[KubernetesのConfigMapを使ってWordpressのphp.iniを書き換える](https://qiita.com/kuninori/items/b5dd582e62255614b437)）を参照し、マウント先を`subPath`を使用してファイル自体へと変更。

```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
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 9000
        volumeMounts:
        - name: php-persistent-storage
          mountPath: /var/www
        - name: laravel-env
          mountPath: /var/www/.env
          # マウント先をファイル自体へと変更する。
          subPath: .env
      volumes:
      # サーバーログ出力先(nginxと共有)
      - name: php-persistent-storage
        persistentVolumeClaim:
          claimName: php-server-pv-claim
      # laravel .envをinject
      - name: laravel-env
        configMap:
          name: laravel-env
          items:
          - key: .env
            path: .env
            mode: 0644
```

その後、Deploymentの作成を実施した。

### mysqlの初期化

dbの初期化を実施。これはphpポッド内でコマンドを実行して行った。(極力手作業を避けたいとはなんだったのか……?)

```
$ php artisan migrate
```

`Migration table created successfully.`というメッセージが表示され、php-mysqlポッド間の通信設定が正常に設定できていることが確認できた。

# Laravelプロジェクトへのアクセス

これでLaravel環境に必要な物が一通り揃ったはずなので、アクセスしてみる。

以下コマンドを実行し、nginxのホスト上のポートを確認しアクセス。

```
[root@kube ~]# kubectl describe service/lnmp-nginx-service | grep NodePort
Type:                     NodePort
NodePort:                 <unset>  32626/TCP
```

<div align="center">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546624/3ed1a933-7b6a-939a-d37a-1fcf3888cf18.png" title="laravel">
</div>

Laravel初期画面が出た！！

(地味にLaravelがブラウザのダークテーマに対応しているのに驚いた。)

# まとめ

`Job`を利用し、Laravelプロジェクトの初期化を実施することができた。

今回は`Job`の実行タスクをプロジェクトの初期化のみとしたが、これを`Github`から取得するタスクに変更したりすることで、実装環境の均等化を図ったりできるんだろか？

でもそうなれば、`Job`の管理が大変になるような気もする。どうやっているんだろう?