在 Google Cloud 中使用 Kubernetes Engine 和 Cloud SQL 正确扩展像 WordPress 这样的有状态应用程序
网上有很多示例展示了如何在 Kubernetes 中运行 WordPress。但这些示例的主要问题在于:它们通常只运行一个 WordPress Pod,无法真正实现扩展。
所以我面临的问题是,我需要一个高度可扩展的 WordPress 设置,这就是我最终想出的办法。
为什么有状态应用程序难以扩展?
这些应用程序直接写入磁盘,而且大多数情况下你无法阻止。这种情况在基于 PHP 且使用某种插件系统的应用程序中尤为常见。因此,文件无法存储在某种存储桶中,而必须存储在应用程序的文件系统中。
你可能会说,像https://de.wordpress.org/plugins/wp-stateless/这样的 Stateless 插件会将数据写入云存储桶。没错,它的确会这样做,但它并不会将插件或某些插件直接写入的文件存储在云存储桶中(这种情况虽然令人遗憾,但却是事实)。
该怎么办?
我们需要一些东西,我们想要一个可扩展的数据库,我们需要某种共享文件库供我们的应用程序使用,以及应用程序本身。
为了简洁起见,我们将直接使用预定义的 WordPress Docker 镜像,但您应该始终尝试根据自身需求对这些 Dockerfile 进行修改。将它们作为基础,然后根据您的需要进行扩展。
所以我们需要一个共享磁盘,而这正是我们遇到的第一个问题。我们需要在 Kubernetes 集群中使用 ReadWriteMany 卷,问题就此开始。云服务提供商并不提供这种卷。
如果您查看Kubernetes 文档,
就会发现 GCEPersistantDisk、AzureDisk 和 AWSElasticBlockStore 都不支持我们的需求。虽然
Google Cloud 的 CloudFileStore 或 AzureFileStore 等服务提供了一些选择,但它们对于我们的需求来说太贵也太大了(我们不需要 1TB 的空间来存储 WordPress 网站,谢谢)。
美国国家森林管理局前来救援
但当我们查看列表时,我们发现了救星:NFS 来帮忙了。让我们创建一个唯一的选项,即连接到 NFS 的 ReadWriteOnce 存储。因此,我们需要一个理想情况下可以在不同区域之间共享的存储类:
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: regionalpd-storageclass
provisioner: kubernetes.io/gce-pd
parameters:
type: pd-standard
replication-type: regional-pd
zones: europe-west3-b, europe-west3-c
我们需要创建销量声明
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
storageClassName: ""
volumeName: nfs
现在让我们创建我们的NFS
apiVersion: v1
kind: Service
metadata:
name: nfs-server
spec:
clusterIP: 10.3.240.20
ports:
- name: nfs
port: 2049
- name: mountd
port: 20048
- name: rpcbind
port: 111
selector:
role: nfs-server
现在我们添加 NFS 本身。好处在于,我们可以使用预定义的服务。
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nfs-server
spec:
replicas: 1
selector:
matchLabels:
role: nfs-server
template:
metadata:
labels:
role: nfs-server
spec:
containers:
- name: nfs-server
image: gcr.io/google_containers/volume-nfs:0.8
ports:
- name: nfs
containerPort: 2049
- name: mountd
containerPort: 20048
- name: rpcbind
containerPort: 111
securityContext:
privileged: true
volumeMounts:
- mountPath: /exports
name: nfs
volumes:
- name: nfs
gcePersistentDisk:
pdName: nfs
fsType: ext4
CloudSQL 如此安全,如此美好
好了,我们已经为静态数据搭建了一个运行中的 NFS。接下来,关键一步是连接 Cloud SQL。假设你已经设置好了 Cloud SQL MySQL 数据库,那么如何将你的 Pod 连接到它呢?
我们使用容器自带的 SQL 代理。这样做的好处是,我们的 MySQL 服务没有暴露,我们可以使用本地主机。是不是很棒?
首先,您需要激活Cloud SQL 管理 API。
您需要创建一个具有实际访问云 SQL 权限的服务帐户。
在这里,我们创建一个拥有 Cloud SQL > Cloud SQL 客户端权限的新角色。
下载创建的私钥,我们需要用这个私钥来访问 SQL 实例。
如果您尚未创建数据库用户,请立即创建。
gcloud sql users create [DBUSER] --host=% --instance=[INSTANCE_NAME] --password=[PASSWORD]
我们需要实例的名称,很简单:
gcloud sql instances describe [INSTANCE_NAME]
或者您也可以在网页界面中找到它: 现在我们将凭据保存到我们的 Kubernetes 实例:
kubectl create secret generic cloudsql-instance-credentials \
--from-file=credentials.json=[PROXY_KEY_FILE_PATH]
kubectl create secret generic cloudsql-db-credentials \
--from-literal=username=[DBUSER] --from-literal=password=[PASSWORD]
所以我们准备好搭建WordPress网站了吗?
让我们首先创建这项服务:
apiVersion: v1
kind: Service
metadata:
name: wlp-service
labels:
app: wlp-service
spec:
type: LoadBalancer
sessionAffinity: ClientIP
ports:
- port: 443
targetPort: 443
name: https
- port: 80
targetPort: 80
name: http
selector:
app: wordpress
好了,现在服务已经启动并运行,只差 Pod 本身了。
我们把它拆分一下,以便我解释。
apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress
labels:
app: wordpress
spec:
replicas: 2
strategy:
type: RollingUpdate
selector:
matchLabels:
app: wordpress
template:
metadata:
labels:
app: wordpress
spec:
containers:
- name: wordpress
image: wordpress:7.3-apache
imagePullPolicy: Always
env:
- name: DB_USER
valueFrom:
secretKeyRef:
name: "cloudsql-db-credentials"
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: "cloudsql-db-credentials"
key: password
ports:
- containerPort: 80
name: wordpress
- containerPort: 443
name: ssl
这样足以运行 WordPress,但不需要数据库或持久性 NFS。让我们逐一添加 Cloud SQL 代理:
- name: cloudsql-proxy
image: gcr.io/cloudsql-docker/gce-proxy:1.11
command: ["/cloud_sql_proxy",
"-instances=[YOUR INSTANCESTRING THAT WE LOOKED UP]=tcp:3306",
"-credential_file=/secrets/cloudsql/credentials.json"]
securityContext:
runAsUser: 2 # non-root user
allowPrivilegeEscalation: false
volumeMounts:
- name: cloudsql-instance-credentials
mountPath: /secrets/cloudsql
readOnly: true
volumes:
- name: cloudsql-instance-credentials
secret:
secretName: cloudsql-instance-credentials
太好了,现在我们可以通过本地主机访问我们的 Cloud SQL 了 :) 它实际上在你的 pod 中添加了第二个容器,该容器会将所有到达 3306 端口的流量代理到我们的 Cloud SQL 实例,而无需将流量暴露给公共网络。
现在我们要将 wp-content 目录挂载到 NFS。
volumeMounts:
- name: my-pvc-nfs
mountPath: "/var/www/html/wp-content"
volumes:
- name: my-pvc-nfs
nfs:
server: 10.3.240.20
path: "/"
你可能会问,马里奥,你为什么要给NFS设置固定IP地址?原因很简单。据我所知,这是唯一一个内部DNS无法正常工作的例子。
就这样,现在我们可以通过创建和hpa来扩展我们的pods。
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: wordpress
namespace: default
spec:
maxReplicas: 10
metrics:
- resource:
name: cpu
targetAverageUtilization: 50
type: Resource
minReplicas: 3
scaleTargetRef:
apiVersion: extensions/v1beta1
kind: Deployment
name: wordpress
我们所有的 WordPress 内容文件都存储在 NFS 服务器上,并在各个实例之间共享。没错,NFS 现在确实是我们的单点故障,但 NFS 比只运行一台机器要稳定得多。如果使用 Redis 之类的缓存或者增加 FPM 缓存,还可以进一步缩短加载时间。
很酷吧?
您对 Kubernetes/云基础知识入门感兴趣吗?请告诉我。
文章来源:https://dev.to/mfahlandt/scaling-properly-a-stateful-app-like-wordpress-with-kubernetes-engine-and-cloud-sql-in-google-cloud-27jh