Docker for MacにKubernetesが搭載された安定版がリリースされ、少し時間が立ちました。
minikubeとのお別れは完了しましたでしょうか?今回は、Laravelで作ったアプリケーションをKubernetes上で動かせるサンプルを作成しました。
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
というコマンドをインストールします。
kubectl
のインストールが完了したら、Kubernetesのパッケージマネージャである helm
をインストール。
後述する Ingress
が使用できるよう helm
を使用しパッケージをインストールします。
$ helm init
$ helm install --name my-release stable/nginx-ingress
最後に、Kubernetesでの開発に便利なツールである skaffold
をインストールしておきます。
諸々のインストールが完了したら、作業を進めていきます。
Dockerイメージ作成
ここからコンテナの準備に入ります。まずは、Laravelを動かすためのDockerイメージを作成します。
サンプルでは、PHPとNginxが共存しているDockerイメージを作成しています。下記記事を参考にさせていただきました。
Dockerコンテナイメージのダイエット - Laravel編
※ このDockerファイルだと、開発時に毎回npm install
やcomposer 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
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
apiVersion: v1
kind: Service
metadata:
name: sample-app-service
spec:
type: NodePort
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: sample-app
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を使用する際に参考にすることもできます。
なお、Services, Ingressに関して詳しく知りたい場合は下記記事が参考になるかと思います。
KubernetesクラスタにServiceとIngressを追加し、Webアプリケーションを外部に公開
ConfigMaps
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
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つです。
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に領域を紐付けるということが可能になります。
詳しくは下記記事が参考になると思います。
Redis
Deployment
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を起動しておきます。
2つともグリーンのまるポチがついてrunningな状態になってればOKです。
クラスタにデプロイ、 開発する
クラスタにデプロイするには、skaffoldを利用するのが便利です。
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を使わないというのも、一つの手かもしれません)