K8S 建立 Laravel LoadBalancer

Gary Ng
26 min readFeb 28, 2024

--

https://miro.medium.com/v2/resize:fit:736/0*GQ9yZONi-jMH4Ei1.png

此教學將會使用到 AWS ECR 將建制的 docker image 推到 aws 的 private docker registry, 並且最後會建置一個 ALB Laravel application 起來。

因為我們會將 docker image 推送到 ECR, 所以我們要先建立 ECR

  1. 進入到 AWS ECR

2. 新增 docker repository

3. 選擇 private 且輸入可以識別的 repository name, 都輸入好後按下 create 即可在 ecr 列表上看到

4. 進入所建立的 ECR, 點選 push command 可以看到上傳 image 到 ECR 的指令

首先我們先建置一個 nginx 相關的服務起來。

  1. 建置 nginx Dockerfile

FROM nginx:latest

COPY ./nginx/config/nginx.conf /etc/nginx/conf.d/web.conf

COPY ./data/index.php /app/index.php

2. ./config/nginx.conf

server {
listen 80;
server_name <server name>;

root /app;
index index.php index.html

error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;

location / {
try_files $uri $uri/ /index.php?$query_string;
}

location ~ .php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+.php)(/.+)$;
fastcgi_pass 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;
}
}

3. index.php

<!DOCTYPE html>
<html>
<body style="background-color:rgb(217, 250, 210);">

<h1>Welcome to Stack Simplify</h1>
<h3>AWS EKS Master Class - Integration with ECR Registry</h3>
<h3><?php echo 'php version: '. phpversion(); ?></h3>
</body>
</html>

index.html 的用途主要是等等先測試看看 nginx service 是否有建置成功所做的範本,所以到時可以移除。

4. build nginx docker image

cd <nginx dockerfile 所在的位置>

docker build --tag <aws ecr tag> -f <nginx Dockerfile 的檔案名稱> .

5. 建置好後可以透過 docker images 查看是否有 build image 出來

docker images
被我蓋住的則是 docker tag 的值

6. 將 docker image 推送到 ecr 使用上面 ecr push command 的步驟, 依照步驟做完後即可在 ECR 看到鎖推送的 image

7. 因為 EKS 要從 ECR pull image 下來所以需要設定 imagePullSecret

# 取得 ecr token
TOKEN=$(aws ecr get-login-password --region <region>)

kubectl create secret docker-registry regcred --docker-server=<ecr> --docker-username=AWS --docker-password=$TOKEN

參考資料如下:

8. 建議完後即可以在 secret 看到所建立的 regcred

kubectl get secret -o wide

透過以下指令可以查看詳細內容

kubectl get secret regcred --output=yaml

9. deployments/nginx.yml

apiVersion: apps/v1
kind: Deployment
metadata:
name: web-deployment
labels:
app: web
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
name: web
labels:
app: web
spec:
containers:
- image: <ecr>
name: nginx
ports:
- containerPort: 80
imagePullSecrets:
- name: regcred

10. 套用 deployment.yml 以及查看所建立的 pods


kubectl apply -f <deployment file>

kubectl get pods -o wide

11. 建立 nginx service.yml

apiVersion: v1
kind: Service
metadata:
name: web-service
labels:
web: service
spec:
# 建立 ALB 且 annotations 為 instance 的話記得要設定為 NodePort
type: NodePort
selector:
app: web
ports:
- port: 80
protocol: TCP
targetPort: 80

12. 套用 service.yml 且查看 service

kubectl apply -f <service yml file>

kubectl get svc

建立 php 相關的 pod

在建立 php 相關的 pod 前記得也需要為 php 新增一個 private registry 在 ECR 上。

php Dockerfile

FROM php:8.1-fpm

COPY ./data/index.php /app/index.php

用以下指令 build php docker image

docker build --tag <php ecr repository> -f <docker file> .

php_deployment.yml

apiVersion: apps/v1
kind: Deployment
metadata:
name: php-deployment
labels:
app: php
spec:
replicas: 1
selector:
matchLabels:
app: php
template:
metadata:
name: app-deployment
labels:
app: php
spec:
containers:
- name: php81-fpm
image: <ecr>/php:latest
ports:
- containerPort: 9000
imagePullSecrets:
- name: regcred

套用 php deployment

kubectl apply -f <php deployment file>

查看 php deployment 是否已建立成功

kubect get pods -o wide

建立 php_service.yml

apiVersion: v1
kind: Service
metadata:
# 在 nginx.conf php-fpm socket 所使用的名稱
name: php-service
labels:
php: service
spec:
selector:
app: php
ports:
- port: 9000
targetPort: 9000

套用 php_service.yml

kubectl apply -f <php service file>

設定 ALB ingresss

緊接著我們要來設定 ALB, 在設定 ALB 之前我們需要安裝所需的套件 helm。

1. 在設定 ingress 之前我們要先安裝 helm, 因為會使用到 aws-load-balancer-controller

brew install helm

安裝完成後按照以下網址的設定進行 role policy 以及安裝步驟

helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=<cluster-name>

假如在安裝時沒有指定 service account name 的話記得要在 EKS worker node role 增加以下網址的 policy

https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.7.0/docs/install/iam_policy.json

2. 透過以下指令檢查 aws-load-balancer-controller 是否正常運行

https://docs.aws.amazon.com/zh_tw/eks/latest/userguide/aws-load-balancer-controller.html

3. 我們要使用ALB 因此要建立一個 alb ingress

web-ingress.yml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-ingress
labels:
app: ingress
annotations:
# 啟用 aws alb
kubernetes.io/ingress.class: alb
# 目標為 instance
alb.ingress.kubernetes.io/target-type: instance
# 對外網的 pod 設定, 假如為內部則 internal
alb.ingress.kubernetes.io/scheme: internet-facing
spec:
rules:
- http:
paths:
- backend:
service:
name: web-service
port:
number: 80
path: /
pathType: Prefix
host: <host name>

4. 套用 ingress 並且檢查是否正常

kubectl apply -f <ingress file>

kubectl get ingress -o wide

5. 在 DNS 設定好之後就可以透過 ingress 設定的 host 連進去查看

建置 Laravel 用的 Service

這邊將會把前面使用的 index.php 改為指向 laravel project,首先我們先來修改 php 的 Dockerfile 為如下

Dockerfile

# 引入 composer2.7 image
FROM composer:2.7 as composer

### 環境準備以及一些參數設定
FROM php:8.1-fpm

# 複製 composer image 的 composer binary 到 php image
COPY --from=composer /usr/bin/composer /usr/bin/composer

# 系統更新
RUN apt-get update

# 安裝系統套件
RUN apt-get install -y libzip-dev \
zip unzip wget zlib1g-dev libicu-dev \
libfreetype6-dev libjpeg62-turbo-dev \
libpng-dev

RUN docker-php-ext-configure gd --with-freetype --with-jpeg

RUN docker-php-ext-install -j$(nproc) gd

# 安裝 php 套件
RUN docker-php-ext-install zip pdo pdo_mysql intl opcache

# 使 composer 可以使用 sudo
ENV COMPOSER_ALLOW_SUPERUSER=1

# 複製專案到 app
COPY ./project/k8s-demo /app

# 切換工作目錄
WORKDIR /app

# 安裝套件
RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist

# 產生 vendor/autoload.php
RUN composer dump-autoload

# 修改 storage 權限
RUN chmod -R 777 storage/

# COPY ./data/index.php /app/index.php

php_deployment.yml 相對應的也要修改,因為跟 nginx 一樣改為從 ECR 抓取 image, 且在初始化 container 的時候執行 migrate

apiVersion: apps/v1
kind: Deployment
metadata:
name: php-deployment
labels:
app: php
spec:
replicas: 2
selector:
matchLabels:
app: php
template:
metadata:
name: app-deployment
labels:
app: php
spec:
affinity:
# 選取節點名稱為 web
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: eks.amazonaws.com/nodegroup
operator: In
values:
- web
# 假如 node 裡面的 pod 已經有 app:label 的話則不加入此 node
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: php
# 在啟服務前先跑 migration
initContainers:
- name: laravel-migration
image: <php ecr image>
command:
- "php"
args:
- "artisan"
- "migrate"
containers:
- name: php81-fpm
image: <php ecr image>
ports:
- containerPort: 9000
imagePullSecrets:
- name: regcred

重新 build php image 以及套用 php_deployment.yml

kubectl apply -f <php deployment file>

因為現在是將 laravel project copy 到 container 內,所以 nginx 的 image 也需要做修改,將 nginx.conf 修改如下

server {
listen 80;
server_name <server name>;

root /app/public;
index index.php index.html

error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;

location / {
try_files $uri $uri/ /index.php?$query_string;
}

location ~ .php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+.php)(/.+)$;
fastcgi_pass 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;
}
}

將 nginx 的 Dockerfile 修改,改為將 php ecr image 的資料複製到 nginx image 裡面

FROM nginx:latest

COPY ./nginx/config/nginx.conf /etc/nginx/conf.d/web.conf

# COPY ./data/index.php /app/index.php

# 將 php image 的資料 copy 到 nginx /app
COPY --from=<php ecr image> /app /app

nginx_deployment 也需要做修改

apiVersion: apps/v1
kind: Deployment
metadata:
name: web-deployment
labels:
app: web
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
name: web
labels:
app: web
spec:
affinity:
# 將 pod 加入至 nodegroup 為 web 的節點群中
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: eks.amazonaws.com/nodegroup
operator: In
values:
- web
# 假如 node 裡的 pod 已經有 app:web 的標籤的話則不加 pod 加入此 node
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: web
containers:
- image: <ecr nginx image>
name: nginx
ports:
- containerPort: 80
imagePullSecrets:
- name: regcred

重新 build nginx image 以及重新套用 nginx_deployment

kubectl apply -f <nginx deployment>

建置 MySQL

因為要使用到 Laravel 所以接下的步驟將會教學如何建置 mysql service。前面的部分我們將 web 相關的服務放在某個 EKS Cluster 裡面,而 mysql 的部分我們為其建立一個 node group 在相同的 EKS Cluster 裡面。

我們先為其 mysql 建置所需的資料庫名稱以及使用者 (ConfigMap)

mysql-config.yml

apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-config
labels:
mysql: config
data:
database: web
user: laravel

套用 mysql-config

kubectl apply -f mysql-config.yml

為 Mysql 建置使用者所需的密碼, 而 secret 的編碼為 base64

mysql-secret.yml

apiVersion: v1
kind: Secret
metadata:
name: mysql-secret
labels:
mysql: secret
data:
root-password: cm9vdA==
user-password: bGFyYXZlbA==

為其 mysql 建立所需的 deployment

mysql-deployment.yml

apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql-deployment
labels:
app: mysql
spec:
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
# 將 mysql 的資源放在名叫 mysql 的 node group
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: eks.amazonaws.com/nodegroup
operator: In
values:
- mysql
containers:
- name: mysql
image: mysql:8.0
ports:
- containerPort: 3306
env:
- name: MYSQL_DATABASE
valueFrom:
configMapKeyRef:
key: database
name: mysql-config
- name: MYSQL_USER
valueFrom:
configMapKeyRef:
key: user
name: mysql-config
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
key: root-password
name: mysql-secret
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
key: user-password
name: mysql-secret

將此 deployment 套用

kubectl apply -f mysql-deployment.yml

為 mysql 建置 service

mysql-service.yml

apiVersion: v1
kind: Service
metadata:
name: mysql-service
labels:
mysql: service
spec:
selector:
app: mysql
ports:
- port: 3306
targetPort: 3306
protocol: TCP

將此 mysql-service.yml 套用

kubectl apply -f mysql-service.yml

都設定完成後直可透過網址看到 Laravel 的 welcome 頁面

建置 Laravel 排程到 node group

建置 cronJob.yml

apiVersion: batch/v1
kind: CronJob
metadata:
name: laravel-schedule
labels:
laravel: schedule
spec:
schedule: "* * * * *"
jobTemplate:
spec:
template:
spec:
affinity:
# 選取節點名稱為 cron
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: eks.amazonaws.com/nodegroup
operator: In
values:
- cron
containers:
- image: <php ecr repository>
name: scheduler
command:
- php
args:
- "artisan"
- "schedule:run"
imagePullSecrets:
- name: regcred
# 先設定為重啟後不會重啟
restartPolicy: Never

套用 cronjob

kubectl apply -f cronJob.yml

此時可以透過 pod 查看 cronJob 是否正常運行

kubectl get pods

參考文件:

https://stackoverflow.com/questions/57494369/kubectl-apply-deployment-to-specified-node-group-aws-eks

--

--

Gary Ng
Gary Ng

Written by Gary Ng

軟體工程師、後端工程師

No responses yet