GKE Autopilot 上 MySQL GCSFuse 权限被拒绝问题解决
2025-03-03 22:51:33
MySQL on GKE Autopilot with GCSFuse: /var/lib/mysql/
权限被拒绝
这问题看着头大,在 GKE Autopilot 上用 GCSFuse 挂载 MySQL,结果 /var/lib/mysql/
目录权限不对,MySQL 起不来。别慌,咱来一步步分析解决。
问题原因分析
核心问题就一个:MySQL 启动时,发现数据目录 /var/lib/mysql/
没法写入,报 "Permission denied"。 这通常有几个可能的原因:
- GCSFuse 挂载问题: 即使 Kubernetes 里 PVC 看着是
ReadWriteMany
,GCSFuse 实际挂载时可能还是只读的,或者权限设置不对。 - 用户权限问题: MySQL 容器内部运行的用户(通常是
mysql
,UID 999)可能对 GCSFuse 挂载的目录没有写权限。虽然你用了initContainer
去chown
,但可能没生效,或者 GCSFuse 有自己的权限控制机制。 - GCSFuse 配置问题: GCSFuse 本身的一些参数可能影响了挂载后的权限。
- 底层 GCS 权限问题: 确保你的服务账号真的有对 GCS 存储桶的读写权限。 尽管你配置了服务账号的权限, 也要复查。
解决方案
下面针对上面几个原因,给出具体的解决方案。
1. 显式指定 GCSFuse 挂载选项
GCSFuse 提供了一些挂载选项,可以更精细地控制权限。咱可以在 Deployment 里通过 gke-gcsfuse-sidecar
的参数来指定这些选项。
- 原理: 通过
--file-mode
和--dir-mode
选项,可以明确指定挂载后的文件和目录权限。--uid
和--gid
可以指定挂载目录所属的用户和组。 - 操作: 修改 Deployment YAML,给
gke-gcsfuse-sidecar
容器添加args
:- 如果在sidecar 模式下,就需要在应用的 container 的YAML 里面加。
- 如果在init container模式下,需要在deployment 对应的 initcontainer 里面添加如下信息.
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-mysql-deployment
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
annotations:
gke-gcsfuse/volumes: "true"
spec:
serviceAccountName: spock
initContainers:
- name: fix-permissions
image: busybox
command: ["sh", "-c", "chown -R 999:999 /var/lib/mysql && chmod -R 777 /var/lib/mysql"]
securityContext:
runAsUser: 0
volumeMounts:
- name: shared-storage
mountPath: "/var/lib/mysql"
subPath: "databases/api"
containers:
- name: gke-gcsfuse-sidecar #修改这个容器
image: gke-gcsfuse-sidecar #镜像名字要对应实际使用的版本
args:
- "--file-mode=777"
- "--dir-mode=777"
- "--uid=999"
- "--gid=999"
# 下面这些设置根据实际情况添加或删除
- "--implicit-dirs" # 如果要自动创建不存在的父目录,就加上
- "--app-name=mysql"
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
runAsUser: 0
seccompProfile:
type: RuntimeDefault
volumeMounts:
- mountPath: /var/lib/mysql
mountPropagation: Bidirectional
name: shared-storage
- name: mysql
image: mysql:8.0
args:
- "--skip-log-bin"
- "--skip-ssl"
- "--ssl=0"
- "--innodb-flush-method=fsync"
- "--innodb-use-native-aio=0"
env:
- name: MYSQL_ROOT_PASSWORD
value: "password"
- name: MYSQL_DATABASE
value: "api"
- name: MYSQL_USER
value: "user"
- name: MYSQL_PASSWORD
value: "password"
volumeMounts:
- name: shared-storage
mountPath: "/var/lib/mysql"
subPath: "databases/api"
volumes:
- name: shared-storage
persistentVolumeClaim:
claimName: shared-storage-pvc
-
说明:
--file-mode=777
和--dir-mode=777
:给所有用户读、写、执行权限。--uid=999
和--gid=999
:让挂载目录属于mysql
用户和组(假设你的 MySQL 容器里mysql
用户的 UID 和 GID 是 999)。--implicit-dirs
: GCSFuse 会自动创建需要的父目录。--app-name
: 可以设置一个应用名称,方便调试。
-
安全建议: 把权限设置为 777 虽然简单粗暴,但不太安全。 生产环境最好根据实际需要,设置更精细的权限(比如 770,只允许所属用户和组有全部权限,其他用户没有任何权限)。
2. 调整 initContainer
的策略
你现在的 initContainer
只是简单地 chown
,可能不够。 咱可以尝试更全面的权限设置方法。
- 原理:
initContainer
在 MySQL 容器启动 之前 运行,可以用来做一些准备工作。 我们可以在这里更细致地设置权限。 - 操作: 修改 Deployment YAML,调整
fix-permissions
这个initContainer
:
initContainers:
- name: fix-permissions
image: busybox
command:
- sh
- -c
- |
mkdir -p /var/lib/mysql/data
chown -R 999:999 /var/lib/mysql
chmod -R 777 /var/lib/mysql # 或者更安全的权限,如 750
# 确保数据目录存在并且权限正确
chown 999:999 /var/lib/mysql/data
chmod 750 /var/lib/mysql/data
volumeMounts:
- name: shared-storage
mountPath: /var/lib/mysql
subPath: "databases/api" # 确保 initContainer 也挂载了相同的 subPath
- 说明:
mkdir -p /var/lib/mysql/data
: 有时候 MySQL 需要/var/lib/mysql
下面有个data
子目录。 咱在initContainer
里提前创建好。- 多次
chown
和chmod
: 确保各个目录的权限都设置正确。
3. 使用 GCSFuse 的 --static-mount
(如果适用)
如果你的 GCS 存储桶里的文件和目录结构 不会 频繁变化(比如,你不会在 GCS 网页控制台里直接修改数据库文件),可以考虑用 --static-mount
。
- 原理: 默认情况下,GCSFuse 是动态挂载的。 每次访问文件时,都会去 GCS 检查一下。
--static-mount
会在启动时把 GCS 存储桶的目录结构 缓存 到本地,后续访问就直接从本地缓存读取,性能更好,也更不容易出现权限问题。 - 操作: 给
gke-gcsfuse-sidecar
容器添加--static-mount
参数(跟上面的--file-mode
等参数一起加):
- name: gke-gcsfuse-sidecar
... # 其他配置
args:
- "--file-mode=777"
- "--dir-mode=777"
- "--uid=999"
- "--gid=999"
- "--implicit-dirs"
- "--static-mount" # 加这行
... # 其他配置
- 注意: 用了
--static-mount
后,如果你在 GCS 网页控制台里直接修改了文件或目录,GCSFuse 挂载的目录里 不会 立即看到变化。 需要重启 Pod 才能刷新。
4. 检查服务账号权限 (复查)
- 原理: 确保 Kubernetes Pod 使用的服务账号,在 Google Cloud 上有足够的权限访问 GCS 存储桶。
- 操作:
- 在 Google Cloud Console 里,找到你的 GKE 集群。
- 找到节点池使用的服务账号。
- 确保这个服务账号有
roles/storage.objectAdmin
或roles/storage.admin
角色。
也可以使用 gcloud 命令行工具检查:
gcloud iam service-accounts describe YOUR_SERVICE_ACCOUNT_EMAIL --format="value(displayName)"
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
--member="serviceAccount:YOUR_SERVICE_ACCOUNT_EMAIL" \
--role="roles/storage.objectAdmin"
(把 YOUR_SERVICE_ACCOUNT_EMAIL
和 YOUR_PROJECT_ID
替换成实际的值)
- 如果你使用了Workload Identity。 检查Kubernetes Service Account 和 Google Service Account之间的绑定关系:
gcloud iam service-accounts get-iam-policy \
GOOGLE_SERVICE_ACCOUNT@PROJECT_ID.iam.gserviceaccount.com \
--project=PROJECT_ID
确认有类似下面的输出:
- members:
- serviceAccount:PROJECT_ID.svc.id.goog[NAMESPACE/KUBERNETES_SERVICE_ACCOUNT]
role: roles/iam.workloadIdentityUser
5. GCSFuse 进阶:单独的 Sidecar 容器(可选)
如果上面这些方法还不行,可以考虑把 GCSFuse 放到一个 单独的 Sidecar 容器里,而不是跟 MySQL 容器放一起。 这样可以更精细地控制 GCSFuse 的启动和配置。
-
原理: 将 GCSFuse 的职责分离出来,可以更好地隔离问题,也方便单独调试。
-
这个操作比较复杂, 而且要充分了解sidecar 模式的配置。 一般情况下不需要这样做.
总结
解决 MySQL on GKE Autopilot with GCSFuse 权限问题,关键是搞清楚 GCSFuse 的挂载机制,以及 Kubernetes 里的权限控制。 上面这几招,一般来说组合起来用,就能解决问题。
记住每次修改后要重启 Pod(kubectl rollout restart deployment/api-mysql-deployment
)才能生效。
Good luck! 祝你早日搞定!