Edited at

Laravelをskaffold使ってKubernetesで動かす

Docker for MacにKubernetesが搭載された安定版がリリースされ、少し時間が立ちました。

minikubeとのお別れは完了しましたでしょうか?今回は、Laravelで作ったアプリケーションをKubernetes上で動かせるサンプルを作成しました。

ryosukes/laravel-k8s-sample

READMEを読めば、とりあえず動かすことは可能になるかと思うので、とりあえず動かしてみたいと思う方は試してみてください(初回はすごく時間かかるかもですが)。

僕自身が社内でLaravelを使っているため、今回はLaravelでサンプルを作成しましたが、フレームワークだったり言語を他のものにすげ替えても簡単に動作させることが可能だと思います。

この記事では、Mac上でLaravel on Kubernetesを動かしskaffoldを使って開発できるようにするまでの流れを解説していきたいと思います。


  • 目次


    • はじめに

    • Dockerイメージ作成

    • マニフェスト作成

    • クラスタにデプロイ、 開発する

    • まとめ




はじめに

必要となるLaravel, Docker for Macなどの準備を終えておきましょう

Laravel Installation

Docker for Mac

もしDocker for Macがインストール済で古いバージョン(Docker Community Edition 18.06.0-ceより古い)の場合は、Kubernetesが使えるよう最新版にアップデートしておきましょう。

次に、ターミナル上でKubernetesを操作するために kubectl というコマンドをインストールします。

https://kubernetes.io/docs/tasks/tools/install-kubectl/

kubectl のインストールが完了したら、Kubernetesのパッケージマネージャである helmをインストール。

https://github.com/helm/helm

後述する Ingress が使用できるよう helm を使用しパッケージをインストールします。

$ helm init

$ helm install --name my-release stable/nginx-ingress

最後に、Kubernetesでの開発に便利なツールである skaffold をインストールしておきます。

https://github.com/GoogleContainerTools/skaffold

諸々のインストールが完了したら、作業を進めていきます。


Dockerイメージ作成

ここからコンテナの準備に入ります。まずは、Laravelを動かすためのDockerイメージを作成します。

サンプルでは、PHPとNginxが共存しているDockerイメージを作成しています。下記記事を参考にさせていただきました。

Dockerコンテナイメージのダイエット - Laravel編

※ このDockerファイルだと、開発時に毎回npm installcomposer installが走りビルドに時間がかかります。

STG/本番リリース用に使う分にはよいですが、開発用にはそれらを省いてビルド時間が短縮できるようイメージを構築できるとローカルでの開発がより楽に進められるようになるかと思います。

(すでにnpm,yarn,composerなどがインストール済みのイメージを別で用意し、composer installなどはローカルにあるvendorをマウントするなど)


マニフェスト作成

マニフェストと呼ばれるYAML形式の設定ファイルを作成し、それをKubernetesのクラスターに適用することで、k8s環境を構築します。

用意するマニフェストは下記。


  • Application(サンプルではLaravel)


    • Deployments

    • Services

    • Ingress

    • ConfigMaps



  • MySQL


    • Deployments

    • PersistentVolumes

    • PersistentVolumeClaims

    • Services



  • Redis


    • Deployments

    • Services



Laravel, MySQL, Redis用で分かれていますが、それぞれ細かく見ていきます。


Application


Deployments


deployment.yaml

apiVersion: extensions/v1beta1

kind: Deployment
metadata:
name: sample-app
spec:
replicas: 2 # Podの数
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 100% # ローリングアップデート時に許容できる超過して作られる Pod の最大数
maxUnavailable: 0 # 最低 1 pod は ready な状態を保つ
template:
metadata:
labels:
app: sample-app
spec:
containers:
- name: sample-app
image: sample-app/app-dev:latest # Dockerイメージを指定
envFrom:
- configMapRef:
name: sample-app-config # configmapの名前を指定
ports:
- containerPort: 80

「いきなり設定ファイル出されてもわかんねーよ!」ということもあるかと思いますが、冷静に見ればそんなに難しいことは書いてありません。

Deploymentsでは、その名の通り、LaravelアプリのDockerイメージが乗っかる Pods と呼ばれるコンテナグループのデプロイ管理定義を行います。

Deployments及びPodsを詳しく知りたい場合は、下記記事などが参考になります。


Services, Ingress


service.yaml

apiVersion: v1

kind: Service
metadata:
name: sample-app-service
spec:
type: NodePort
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: sample-app


ingress.yaml

apiVersion: extensions/v1beta1

kind: Ingress
metadata:
name: sample-app-ingress
annotations:
kubernetes.io/ingress.class: "nginx"
ingress.kubernetes.io/ssl-redirect: “true”
ingress.kubernetes.io/rewrite-target: /
spec:
tls:
- hosts:
- sample-laravel.localhost
secretName: tls-certificate
rules:
- host: sample-laravel.localhost
http:
paths:
- path: /
backend:
serviceName: sample-app-service
servicePort: 80

ServiceとIngressでは、Kubernetes内でのネットワークの設定を行っています。Serviceでportや通信プロトコルを指定、IngressはAWSのALBのように、受け付けたリクエストのパスなどに応じてどのアプリケーションにリクエストを流すかの設定ができます。

また、Ingressではsslの設定を行うこともできます。サンプルではopensslで証明書を作成し、その証明書を使用しています。

# set common name 'sample-laravel.localhost'

$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /tmp/nginx-selfsigned.key -out /tmp/nginx-selfsigned.crt; openssl dhparam -out /tmp/sample.pem 2048
$ kubectl create secret tls tls-certificate --key /tmp/nginx-selfsigned.key --cert /tmp/nginx-selfsigned.crt

Ingressではベーシック認証などもかけることができるので、ステージングでKubernetesを使用する際に参考にすることもできます。

https://github.com/kubernetes/contrib/tree/master/ingress/controllers/nginx/examples/auth

なお、Services, Ingressに関して詳しく知りたい場合は下記記事が参考になるかと思います。

KubernetesクラスタにServiceとIngressを追加し、Webアプリケーションを外部に公開


ConfigMaps


configmap.yaml

apiVersion: v1

kind: ConfigMap
metadata:
name: sample-app-config
data:
# ------------------------------------------------------------------------------
# app
# ------------------------------------------------------------------------------

APP_NAME: laravel
APP_ENV: local
APP_DEBUG: "true"
APP_URL: https://sample-laravel.localhost
#APP_KEY:

# NOTE: to use stern
LOG_CHANNEL: errorlog

# ------------------------------------------------------------------------------
# DB
# ------------------------------------------------------------------------------

DB_HOST: mysql
DB_USERNAME: root
DB_PASSWORD: root

DB_CONNECTION: mysql
DB_DATABASE: sample
DB_DATABASE_TEST: sample_test

REDIS_HOST: redis-master

# ------------------------------------------------------------------------------
# MAIL
# ------------------------------------------------------------------------------

MAIL_DRIVER: smtp
MAIL_HOST: smtp.mailtrap.io
MAIL_PORT: "2525"
MAIL_USERNAME: "null"
MAIL_PASSWORD: "null"
MAIL_ENCRYPTION: "null"


ConfigMapではLaravelの .env に設定する値など環境変数を記載します。ここは .envをそのまま使うでも良いと思いますが、ビルドしたアプリケーションイメージに.envを含めたくないといった場合、ConfigMapを使うと良いでしょう。また、本番運用を見越して使うのであれば、DBやメールなどの秘匿情報に関しては、KubernetesのSecretを使うのが良いです。KMSなどを使えば、安全に運用できます。

ファイルをAWS KMSで暗号化して安全にgit commitできるようにするmozilla/sopsの使い方


MySQL

続いてMySQLのPodの設定を見ていきます。


Deployments


mysql-deployment.yaml

apiVersion: apps/v1

kind: Deployment
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql
strategy:
type: Recreate
template:
metadata:
labels:
app: mysql
spec:
containers:
- image: mysql:5.7
name: mysql
resources:
requests:
memory: 100Mi
cpu: 100m
limits:
memory: 4000Mi
env:
# Use secret in real usage
- name: MYSQL_ROOT_PASSWORD
value: root
- name: MYSQL_DATABASE
value: sample
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
volumes:
- name: mysql-persistent-storage
persistentVolumeClaim:
claimName: mysql-pv-claim

LaravelのDeploymentでは記載がありませんでしたが、cpuとmemoryの指定をしています。過去に作業していたとき、Sequel Proを使用して大きめなダンプデータを流したところフリーズしたことがありました。そのときに、cputとmemoryの割当を増やしスペックを上げたところフリーズしなくなったことがあります。この値は適宜変更して使えると良いと思います。

また、データの永続化をするためにvolumesで persistentVolumeClaims の設定をしています。このあたりは次で説明します。


PersistentVolumes, PersistentVolumeClaims

データに関して、Podやクラスターが終了してもなくならないよう、データの永続化を行う必要があります。そのためにあるのが、PersistentVolumes および PersistentVolumeClaimsの2つです。


mysql-pv.yaml

kind: PersistentVolume

apiVersion: v1
metadata:
name: mysql-pv-volume
labels:
type: local
spec:
storageClassName: manual
capacity:
storage: 20Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/var/lib/mysql"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pv-claim
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi

サンプルではそれぞれの設定の記述が短いため一つのyamlファイルにまとめてしまっています。このマニフェストを用意しDeploymentsで設定を記述することで、PersistentVolumes でストレージ領域を確保、そのストレージを PersistentVolumeClaimes でPodに領域を紐付けるということが可能になります。

詳しくは下記記事が参考になると思います。

Kubernetsに出てくる用語:PVCとは?


Redis


Deployment


redis-deployment.yaml

apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2

kind: Deployment
metadata:
name: redis-master
labels:
app: redis
spec:
selector:
matchLabels:
app: redis
role: master
tier: backend
replicas: 1
template:
metadata:
labels:
app: redis
role: master
tier: backend
spec:
containers:
- name: master
image: redis
resources:
requests:
cpu: 100m
memory: 100Mi
ports:
- containerPort: 6379

特筆するするところはないのですが、roleやreplicasを変更したマニフェストを作成するなどしmaster -slave のクラスタ構成を作成することも可能です。


クラスタを起動する

さて、以上でDockerとKubernetesの準備が完了したところで、Docker for MacでDocker/Kubernetesを起動しておきます。

領域のキャプチャ 29.png

Kubernetes 2018-11-17 15-55-51.png

2つともグリーンのまるポチがついてrunningな状態になってればOKです。


クラスタにデプロイ、 開発する

クラスタにデプロイするには、skaffoldを利用するのが便利です。

skaffold.png

skaffoldはアプリケーションコードが含まれたDockerイメージをKubernetes上にデプロイするのに便利なGoogle謹製のツールです。

skaffoldのリポジトリにskaffold用の設定サンプルファイルがあるのでそれを編集します。ファイルはこれ

設定を変更したものが

apiVersion: skaffold/v1alpha2

kind: Config
build:
tagPolicy:
sha256: {}
artifacts:
- imageName: sample-app/app-dev # 好きなコンテナ名を指定してOK
workspace: .
docker:
dockerfilePath: Dockerfile # skaffold.yamlと同じディレクトリ
local:
skipPush: true
deploy:
kubectl:
manifests:
- kubernetes/app/* # skaffoldがmanifestファイルの変更を反映してくれる

になります。

この設定ファイルが用意できたら、skaffold run というコマンドを叩くとKubernetesのクラスター上にアプリケーションをデプロイできるようになります。

開発するときは、 ファイルの変更が発生したらすぐ自動更新したくなると思います。そこで、skaffold devというコマンドを叩くとしてくれるようになります。

詳しくは下記記事が参考になるかと思います。

Kubernetesの開発環境で困っているならskaffoldを使え


まとめ

以上で、Kubernetes上でLaravelが動かせるようになったのではないかと思います。Kubernetesは従来のWebの開発環境ではあまり目・耳にしたことがないような単語や概念が多く、学習コストが高めです。しかし、マイクロサービス化を進めたり、安定したサービスを提供するのにとても有用なツールとなっていると思います。

今回Laravelで試しに作ってみて、フルスタックなフレームワークで開発したアプリケーションを載せようとすると、容量が大きいため開発時にKuberenetes上に反映するのに多少時間がかかるなぁと思いました。特にDockerファイルのMulti Stage Buildでnpm installやらアセットのコンパイルがビルド時のタスクとしてあると、べらぼうに時間がかかります。Dockerイメージをビルドする前後で、何かしら工夫する必要があるなと感じました。

コンテナ技術をうまく利用し、より安全にスケーラブルなアプリを作るのであれば、こういったツールの採用とともに、言語・フレームワーク・サービスの境界などの見直しが行えるとよりメリットを享受できそうです。

既存サービスでの採用は難しいところもあるかと思いますが、新規で開発するのであればKubernetesを使わない手はないなと思います。(既存サービスでも、思い切ってローカルではKubernetesを使わないというのも、一つの手かもしれません)