本文僅針對 macOS 環境,全文目標為部署一個 Laravel 10 專案至 GKE。
- 使用 GKE 運行 Laravel 應用程式
- 使用 Google ManagedCertificate 自動管理 SSL 憑證
- 使用 Cloudfare 作為反向代理並使用 SSL 完整或嚴格模式
安裝設定工具
開始之前,您需要建立一組 Google Cloud 的帳號,建立專案,並啟用相關 API 權限。
安裝 Gloud SDK (gcloud
)- Google Cloud 管理指令介面
請先檢查系統環境,須支援 python 3.8 - 3.11。下面使用 pyenv
安裝 python 3.11.4。
若 macOS 系統版本符合您可以直接略過本章節。
1 2 3 4 5 6 7 8
| $ brew install pyenv $ echo 'eval "$(pyenv init -)"' >> ~/.zshrc $ source ~/.zshrc $ pyenv install 3.11.4 $ pyenv global 3.11.4 $ pyenv rehash
$ python -V
|
安裝 Google Cloud CLI
因為部署的目標是 GKE 因此須先安裝 Google Cloud CLI 352.0+ 請依照教學下載對應系統的檔案並設定。
常用指令如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| $ gcloud projects list
$ gcloud config list
$ gcloud config get-value project
$ gcloud config set project [PROJECT_ID] $ export PROJECT_ID="$(gcloud config get-value project -q)" $ gcloud config set compute/zone [REGION]
$ gcloud config configurations list $ gcloud config configurations create [CONFIGURATION_NAME] $ gcloud config configurations activate [CONFIGURATION_NAME]
$ gcloud config set project [PROJECT_ID] $ gcloud config set compute/zone [ZONE] $ gcloud config set account [ACCOUNT]
|
安裝 Kubernetes 客戶端指令工具 (kubectl
)
使用 gcloud
指令安裝 Kubernetes 指令工具 kubectl
:
1 2 3 4
| $ gcloud components install kubectl
$ gcloud components update
|
建立 Kubernetes 叢集
1 2 3 4 5 6
| $ gcloud container clusters create-auto [CLUSTER_NAME] \ --region=[REGION]
$ gcloud container clusters describe [CLUSTER_NAME] --region=[REGION]
|
在建立叢集的時候有 create-auto
和 create
可以使用,create-auto
建立的是 Autopilot 是一種全自動化的叢集管理模式。create
建立的是標準模式叢集,主要差異為標準模式您可以擁有更多控制權。
接著,我們需要驗證叢集的權限,使用下面指令:
1
| $ gcloud container clusters get-credentials [CLUSTER_NAME] --location [REGION]
|
這個指令會提供 kubectl
所需的相關設定。
檢查 kubectl
設定:
1 2 3 4 5 6 7 8 9 10
| $ kubectl config get-contexts
$ kubectl config delete-context [CONTEXT_NAME]
$ gcloud config set project [PROJECT_ID] $ gcloud container clusters get-credentials [CLUSTER_NAME] --location [REGION] $ kubectl config get-contexts
|
配置固定IP
預留一個適用於 Ingress 的全域靜態IP。
使用 Google Kubernetes Engine (GKE) 的 Ingress 資源時,通常需要使用全域靜態 IP 地址。這是因為 Ingress 作為一個 HTTP(S) 負載平衡器,通常需要能夠從任何地方接收到來的網路流量。全域靜態 IP 地址確保了這一點。
1
| $ gcloud compute addresses create [NAME] --global
|
查詢剛建立的IP
1
| $ gcloud compute addresses describe [NAME] --global --format="get(address)"
|
建立 GKE Namespace
Namespace 用於分隔應用,簡單的應用其實可以省略,但我們會使用 Secrets / ConfigMap,如果有多個相同類型的 app 可能導致 .env 混亂,因此需要 Namespace。
假設您有多個 Laravel 應用,每個應用都有自己的 .env
文件。在這種情況下,您可以為每個 Laravel 應用創建一個單獨的 Namespace。然後在每個 Namespace 中創建一個專用的 Secrets,來存儲該應用的配置。
建立 namespace.yaml
:
1 2 3 4
| apiVersion: v1 kind: Namespace metadata: name: [NAMESPACE]
|
執行
1
| $ kubectl apply -f namespace.yaml
|
使用 .env 建立 GKE Secret
在 GKE Secret 中建立環境變數,如此 Docker 建置的時候就可以略過處理 .env
1 2
| $ kubectl create secret generic [SECRET_NAME] --from- env-file [ENV_FILENAME] -n [NAMESPACE]
|
[SECRET_NAME]
名稱後續在 Deployment 的 envFrom
使用。
Laravel 應用容器化
Dockerfile
建立 Dockerfile
for Laravel 10.x
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| FROM php:8.2.12-fpm
WORKDIR /var/www/[PROJECT_FOLDER] COPY . /var/www/[PROJECT_FOLDER]
COPY ./www.conf /usr/local/etc/php-fpm.d/www.conf COPY ./php-fpm.conf /usr/local/etc/php-fpm.conf
RUN apt-get update \ && apt-get install -yqq \ g++ \ libjpeg-dev \ libpng-dev \ libicu-dev \ ssh-client \ unzip \ git \ nginx \ && docker-php-ext-install \ gd \ opcache \ pdo \ pdo_mysql \ intl
RUN pecl install redis RUN cp /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini RUN echo "extension=redis.so" > /usr/local/etc/php/php.ini
RUN { \ echo 'opcache.memory_consumption=128'; \ echo 'opcache.interned_strings_buffer=8'; \ echo 'opcache.max_accelerated_files=4000'; \ echo 'opcache.revalidate_freq=2'; \ echo 'opcache.fast_shutdown=1'; \ echo 'opcache.enable_cli=1'; \ } > /usr/local/etc/php/conf.d/php-opocache-cfg.ini
COPY ./nginx-config /etc/nginx/sites-enabled/default
COPY ./entrypoint.sh /etc/entrypoint.sh
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer RUN composer install --no-dev
RUN usermod -u 1000 www-data COPY --chown=www-data:www-data . /var/www/[PROJECT_FOLDER]
EXPOSE 80 443
ENTRYPOINT ["sh", "/etc/entrypoint.sh"]
|
本地測試
部署之前您可以選擇在本地先進行測試:
⚠️ 注意您的 Dockerfile 須複製處理 .env
,因為上面我們將 .env
丟進 GKE Secret 中,因此在本地測試時須先處理好。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| $ docker build -t [IMAGE_NAME] .
$ docker build --platform=linux/amd64 -t [IMAGE_NAME] .
$ docker run -d -p 8080:80 [IMAGE_NAME]
$ docker ps
$ docker logs [CONTAINER_ID]
$ docker stop [CONTAINER_ID]
$ docker rm [CONTAINER_ID]
$ docker rmi [IMAGE_NAME]
|
本地建置 GKE 映像檔並使用 Artifact Registry
前置作業:
- 啟動 Artifact Registry API
- 安裝 gcloud
- 安裝 docker
開始執行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| $ gcloud artifacts repositories create [REPOSITORY_NAME] \ --repository-format=docker \ --location=asia-east1 \ --project=[PROJECT_ID]
$ gcloud artifacts repositories list
$ gcloud auth configure-docker asia-east1-docker.pkg.dev
$ docker tag [IMAGE_NAME] asia-east1-docker.pkg.dev/[PROJECT_ID]/[REPOSITORY_NAME]/[IMAGE_NAME]:latest $ docker push asia-east1-docker.pkg.dev/[PROJECT_ID]/[REPOSITORY_NAME]/[IMAGE_NAME]:latest
$ docker build --platform=linux/amd64 -t asia-east1-docker.pkg.dev/[PROJECT_ID]/[REPOSITORY_NAME]/[IMAGE_NAME]:latest . $ docker push asia-east1-docker.pkg.dev/[PROJECT_ID]/[REPOSITORY_NAME]/[IMAGE_NAME]:latest
|
Kubernetes 部署
設定 Deployment
Kubernetes 本質就是利用這些 YAML 檔案來設定配置相關服務。接下來我們設定 Deployment 就是這裡會使用到我們前面準備的映像檔和環境變數,
Deployment 負責定義和控制如何執行應用程式,當我們建立一個 Deployment 它會建立並管理 Pod 並讓這些 Pod 運行我們的 Laravel 應用程式。
⚠️ Deployment 的設定會需要之前建立的 Namespace, Secret,Docker Image URL 請確認您已經建立。
建立 deployment.yaml
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| apiVersion: apps/v1 kind: Deployment metadata: name: [DEPLOYMENT_NAME] namespace: [NAMESPACE_NAME] spec: replicas: 1 selector: matchLabels: app: [APP_NAME] template: metadata: labels: app: [APP_NAME] spec: containers: - name: [APP_NAME] image: asia-east1-docker.pkg.dev/[PROJECT_ID]/[REPOSITORY_NAME]/[IMAGE_NAME]:latest imagePullPolicy: Always ports: - containerPort: 80 envFrom: - secretRef: name: [SECRET_NAME] resources: requests: memory: "512Mi" cpu: "500m" limits: memory: "2Gi" cpu: "2000m"
|
執行
1
| $ kubectl apply -f deployment.yaml
|
(Debug) 部署 Deployment 失敗
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| kubectl get pods -n [NAMESPACE]
kubectl describe pod [POD_NAME] -n [NAMESPACE]
kubectl logs [POD_NAME] -n [NAMESPACE]
kubectl get deployments -n [NAMESPACE]
kubectl describe deployment [DEPLOYMENT_NAME] -n [NAMESPACE]
kubectl get nodes
kubectl describe node [NODE_NAME]
|
⚠️「exec format error」通常與架構不匹配有關。大概率為使用 macOS ARM 架構建置 x86 環境使用的 Docker Image。
設定 Service
Service 則是提供一個接口,存取運行在 Pod 中的應用。常見的類型包含 ClusterIP
,NodePort
, LoadBalancer
Service 設定參數的重點:
- Service Selector:
Service.spec.selector
定義了一組標籤,這些標籤用於 Service 查找並連接到一組 Pod。
- Pod 標籤:Pod 的標籤通常在其所屬的 Deployment 的 Pod 模板中定義,位於
Deployment.spec.template.metadata.labels
。
- 匹配標籤:為了讓 Service 正確地將流量轉發到特定的 Pod,Service 的
selector
標籤必須與 Pod 的標籤相匹配。
- Deployment Selector:
Deployment.spec.selector.matchLabels
是用來確保 Deployment 正確管理(創建和更新)其 Pod。這個選擇器應與 Pod 模板中定義的標籤相匹配。
也就是說設定 Service 時要注意 Deployment 對應的標籤設定。
建立 service.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13
| apiVersion: v1 kind: Service metadata: name: [SERVICE_NAME] namespace: [NAMESPACE] spec: selector: app: [APP_NAME] ports: - protocol: TCP port: 80 targetPort: 80
|
執行
1
| $ kubectl apply -f service.yaml
|
SSL 憑證託管
建立 managed-certificate.yaml
1 2 3 4 5 6 7 8
| apiVersion: networking.gke.io/v1 kind: ManagedCertificate metadata: name: [CERT_NAME] namespace: [NAMESPACE_NAME] spec: domains: - [YOUR_DOMAIN]
|
執行
1 2 3 4
| $ kubectl apply -f managed-certificate.yaml
$ kubectl get managedcertificate -n [NAMESPACE]
|
除了指令外,也可以到 GCP 的 Certificate Manager 介面傳統版憑證查詢。
⚠️ SSL 憑證必須要有 DNS 解析,並驗證成功 Active。
⚠️ 因為後續我們會使用 Cloudflare,為了讓憑證生效,注意 Cloudflare 得先使用 DNS Only。
設定 Ingress
建立 SSL Policy
到 GCP > 網路 > SSL 政策 > 建立 Policy > Global SSL policy 。
建立 Ingress
建立 ingress.yaml
,由於 BackendConfig 和 FrontendConfig 關聯性比較緊密,因此彙整在同一個檔案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: [INGRESS_NAME] namespace: [NAMESPACE] annotations: kubernetes.io/ingress.global-static-ip-name: [IP_NAME] networking.gke.io/managed-certificates: [CERT_NAME] kubernetes.io/ingress.class: 'gce' networking.gke.io/v1beta1.FrontendConfig: [FRONTEND_CONFIG_NAME] spec: rules: - host: [YOUR_DOMAIN] http: paths: - path: / pathType: Prefix backend: service: name: [SERVICE_NAME] port: number: 80 --- apiVersion: cloud.google.com/v1 kind: BackendConfig metadata: name: [BACKEND_CONFIG_NAME] namespace: [NAMESPACE] spec: connectionDraining: drainingTimeoutSec: 60
--- apiVersion: networking.gke.io/v1beta1 kind: FrontendConfig metadata: name: [FRONTEND_CONFIG_NAME] namespace: [NAMESPACE] spec: sslPolicy: [POLICY_NAME] redirectToHttps: enabled: true
|
執行
1 2 3 4
| $ kubectl apply -f ingress.yaml
$ gcloud compute addresses list
|
(Debug) Service, Ingress 無法連線
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| $ kubectl get ingress -n [NAMESPACE]
$ kubectl describe ingress [INGRESS_NAME] -n [NAMESPACE]
$ kubectl get svc -n [NAMESPACE]
$ kubectl describe svc [SERVICE_NAME] -n [NAMESPACE]
$ kubectl describe managedcertificate [CERTIFICATE_NAME] -n [NAMESPACE]
|
連線 Pod
1 2 3 4 5
| $ kubectl get pods -n [NAMESPACE]
$ kubectl exec -it [POD_NAME] -n [NAMESPACE] -- /bin/sh
|