多种方案图文并茂分分钟教你解决Kubernetes(k8s)容器安全问题(不断更新中)
@
- 1. Kubernetes(k8s)Secrets in clear text environment variables 明文中的秘密环境变量
- 2. Container is running as root 容器以root 身份运行
- 3. bind() to 0.0.0.0:80 failed (13: Permission denied)
- 4. Do not disable default seccomp profile 不禁用默认的seccomp配置文件
- 5. Restrict container from acquiring additional privileges 限制容器获得额外特权
- 6. Mount container's root filesystem as read only 将容器的根文件系统装载为只读
- 7. Read-only file system 问题解决(文件夹只读问题解决:如果你要在readOnlyRootFilesystem: true状态下,读写一个文件夹)
- 8. Read-only file system 问题解决(单个文件只读问题解决:如果你要在readOnlyRootFilesystem: true状态下,读写单个文件)
- 9. [spec.jobTemplate.spec.template.spec.volumes[3].configMap.defaultMode: Invalid value: 755: must be a number between 0 and 0777 (octal), both inclusive
- 10. 使用Init Containers初始化你的文件夹或者文件~(超简单解决readOnlyRootFilesystem: true情况下项目文件或者文件夹出现Read-only file system或者lock file on a read-only directory:等问题)
1. Kubernetes(k8s)Secrets in clear text environment variables 明文中的秘密环境变量
问题详解
- 在Kubernetes(k8s)中,Secrets用于管理诸如密码、令牌和密钥等敏感信息。安全地存储这些密钥至关重要。然而,将密钥直接注入到明文环境变量中可能会使它们面临潜在的安全风险。
- 在 Kubernetes 集群中,容器能够通过环境变量查询到其他 Service的信息,是因为Kubernetes 在调度Pod时,会自动为每个Service创建一组环境变量。这些环境变量包含了Service的名称、Cluster IP 地址(内部虚拟 IP)、以及 Service 监听的端口等信息。这是 Kubernetes 内置的服务发现机制的一部分,旨在帮助集群内的 Pod 之间进行通信。
- Kubernetes通过环境变量为每个Service创建一个服务发现记录。这些记录允许Pod通过Service 名称来发现和连接到其他 Service,而无需知道它们的IP 地址或端口。
解决方案一(超级推荐解决方案三)
STEP1: 创建Kubernetes Secret - 编写一个yaml文件
注意,这里的值需要进行base64编码
apiVersion: v1
kind: Secret
metadata:
name: 我的密钥
type: Opaque
data:
PASSWORD: <base64编码的密码>
API_KEY: <base64编码的API密钥>
STEP2: 部署执行上述yaml
STEP3: 将Secret作为卷挂载到Pod中-在项目部署K8S.yaml中挂载Secrets
相较于直接注入环境变量,更安全的做法是将Secret作为卷挂载到Pod中。这样,密钥数据将以文件形式存在于容器的文件系统中。
apiVersion: v1
kind: Pod
metadata:
name: 我的Pod
spec:
containers:
- name: 我的容器
image: 我的镜像
volumeMounts:
- name: 密钥卷
mountPath: /var/密钥
readOnly: true
volumes:
- name: 密钥卷
secret:
secretName: 我的密钥
代码实战案例
下面是我写过的一个的sqlserver创建yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: xiaojin-sqlserver-dev-deployment
spec:
replicas: 1
selector:
matchLabels:
app: xiaojin-sqlserver-dev
template:
metadata:
labels:
app: xiaojin-sqlserver-dev
spec:
terminationGracePeriodSeconds: 30
hostname: mssqlinst
securityContext:
fsGroup: 10001
containers:
- name: xiaojin-sqlserver-dev
image: mcr.microsoft.com/mssql/server:2022-latest
ports:
- containerPort: 1433
env:
- name: MSSQL_PID
value: "Developer"
- name: ACCEPT_EULA
value: "Y"
- name: MSSQL_SA_PASSWORD
valueFrom:
secretKeyRef:
name: xiaojin-sqlserver-dev
key: MSSQL_SA_PASSWORD
---
apiVersion: v1
data:
MSSQL_SA_PASSWORD: WGlhb2ppbkAxMjNoYWhh # 等同于密码Xiaojin@123haha
kind: Secret
metadata:
creationTimestamp: null
name: xiaojin-sqlserver-dev
namespace: xiaojin
---
apiVersion: v1
kind: Service
metadata:
name: xiaojin-sqlserver-dev
namespace: xiaojin
spec:
type: NodePort
selector:
app: xiaojin-sqlserver-dev
ports:
- name: xiaojin-sqlserver-dev
protocol: TCP
port: 1433
targetPort: 1433
nodePort: 30034
type: NodePort
解决方案二(超级推荐解决方案三)
STEP1: 创建Kubernetes Secret - 编写一个yaml文件
注意,这里的值需要进行base64编码
apiVersion: v1
kind: Secret
metadata:
name: 我的密钥
type: Opaque
data:
PASSWORD: <base64编码的密码>
API_KEY: <base64编码的API密钥>
STEP2: 部署执行上述yaml
STEP3: 将密钥作为环境变量使用-在项目部署K8S.yaml中直接配置env
如果你非要将密钥作为环境变量使用,这种方式比直接文件挂载安全性稍低,写法如下(下面代码是复制到你的k8s.yaml部署文件里哦)
env:
- name: PASSWORD
valueFrom:
secretKeyRef:
name: 我的密钥
key: PASSWORD
解决方案三(超级推荐)
在Kubernetes中,挂载Secret以便容器能够安全地访问敏感信息(如密码、API密钥等)是一种常见做法。
创建Secret
同上,可以参考上面方案中的创建步骤
挂载Secret
apiVersion: v1
kind: Pod
metadata:
name: my-app-secret
spec:
containers:
- name: my-app-container
image: my-app-image
volumeMounts:
- name: secret-volume # 与volumes中定义的卷名称对应
mountPath: "/app/secrets" # 容器内挂载路径
readOnly: true # 为了安全,通常设置为只读
volumes:
- name: secret-volume # Secret卷定义
secret:
secretName: my-secret-name # Secret的名称
实战案例
2. Container is running as root 容器以root 身份运行
问题详解
当容器以root身份运行的情况下,具有包括但不限于以下几点影响:
- 最高权限访问:容器内的进程可以不受限制地访问宿主机的资源,包括文件系统、网络接口和其他运行中的进程。
- 修改系统配置:能够修改系统级别的配置文件,安装系统包,甚至修改内核参数。
- 安全风险:如果容器内的服务或应用程序存在安全漏洞,攻击者可以利用root权限对整个系统造成严重影响,包括数据窃取、植入恶意软件或完全控制宿主机。
解决方案一:制作镜像的时候就使用非root用户运行(较为麻烦,需要重新打包镜像,建议先尝试方案二,如果不行再参考这个)
Kubernetes推荐使用非root用户运行容器,以遵循最小权限原则。通过指定容器以普通用户身份运行,可以显著降低潜在的安全风险。
- (CIS_Docker_v1.2.0 - 4.1) Image should be created with a non-root user 或者遇到 /var/run/nginx.pid" failed (13: Permission denied)也一样可以采用此方案解决
解决思路:修改Dockerfile或容器镜像,确保应用可以在非root用户下正常运行,使用非root用户运行容器
dockerfile加入下面代码
RUN adduser -D xiaojin
USER xiaojin
k8s yaml 加入下面代码
securityContext:
runAsUser: 0
按照上面的方案修改以后,如果是nginx,可能还会遇到其他问题,我把你可能遇到的问题处理方案放在后面啦
解决方案二:使用Security Context(设置简单方便,推荐这种方案)
- Kubernetes提供了securityContext字段,允许用户在Pod或容器级别设置安全策略,比如指定运行用户(runAsUser)和组(runAsGroup),以及是否允许容器以特权模式运行(privileged)。
- 我们平时遇到的:运行脚本无权限/bin/sh: ./bxxx.sh: Permission denied ,也可以通过这种方案解决
上代码
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
securityContext:
runAsUser: 1000
containers:
- name: my-container
image: my-image
securityContext:
runAsNonRoot: true # 重点代码
runAsUser: 1000 # 重点代码
代码实战案例
3. bind() to 0.0.0.0:80 failed (13: Permission denied)
XXXXXXXX pm2023/06/05 08:04:39 [warn] 1#1: the "user" directive makes sense only if the master process runs with super-user privileges, ignored in /etc/nginx/nginx.conf:2
XXXXXXXX pmnginx: [warn] the "user" directive makes sense only if the master process runs with super-user privileges, ignored in /etc/nginx/nginx.conf:2
Mon, Jun 5 2023 4:04:41 pm2023/06/05 08:04:39 [emerg] 1#1: bind() to 0.0.0.0:80 failed (13: Permission denied)
Mon, Jun 5 2023 4:04:41 pmnginx: [emerg] bind() to 0.0.0.0:80 failed (13: Permission denied)
问题分析:
某些情况下,非Root用户不能绑定1024以下端口,否则会报错:没有权限绑定该端口
解决方案一:镜像配置+yaml配置
- 修改Dockerfile中的端口配置
- nginx.conf中的端口监听
- k8s.yml中的服务端口暴露配置
- containerPort配置
所有你项目中nginx绑定的都要修改一遍哦~~
代码举例:
解决方案二:使用Security Context(设置简单方便,推荐这种方案)
- Kubernetes提供了securityContext字段,允许用户在Pod或容器级别设置安全策略,比如指定运行用户(runAsUser)和组(runAsGroup),以及是否允许容器以特权模式运行(privileged)。
- 我们平时遇到的:运行脚本无权限/bin/sh: ./bxxx.sh: Permission denied ,也可以通过这种方案解决
StartError (failed to create containerd task: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: error mounting "/var/lib/kubelet/pods/149aaaaaa/volume-xxx/xxx/0" to rootfs at "/app/logs": mount /var/lib/kubelet/pods/149xxx/0:/app/logs (via /proc/self/fd/6), flags: 0x5001: not a directory: unknown)
/bin/sh: ./xxx.sh: Permission denied
上代码
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
securityContext:
runAsUser: 1000
containers:
- name: my-container
image: my-image
securityContext:
runAsNonRoot: true # 重点代码
runAsUser: 1000 # 重点代码
代码实战案例
4. Do not disable default seccomp profile 不禁用默认的seccomp配置文件
问题详解
在Kubernetes(k8s)中,Seccomp(Secure Computing Mode)是一种Linux内核安全特性,用于限制容器内进程可以执行的系统调用,以此增强容器的安全性并降低潜在的攻击面。Kubernetes默认为Pod提供了一个基本的Seccomp配置文件(默认配置文件),这个配置文件定义了一系列允许或拒绝的系统调用规则,旨在阻止不安全或不必要的系统调用行为。不要禁用默认Seccomp配置文件的原因包括:
- 减少攻击面:默认的Seccomp配置有助于防止容器执行危险的系统调用,如那些可以修改系统时间、重启系统或执行其他对宿主机有潜在危害的操作。这减少了容器逃逸攻击的可能性。
- 安全最佳实践:遵循Kubernetes社区推荐的安全最佳实践,其中就包括保留默认Seccomp配置。这有助于维护一个统一的安全标准,使得集群管理更加规范和安全。
- 细粒度控制:虽然默认配置已经足够安全,但Kubernetes还允许自定义Seccomp配置以达到更细粒度的控制。禁用默认配置意味着放弃这种细粒度控制的机会,除非有明确且必要的理由去定制化配置。
- 性能影响小:启用Seccomp对容器的性能影响微乎其微,尤其是在使用默认配置时。因此,保持默认配置开启不会对应用的运行效率产生显著负面影响。
- 易于升级和维护:随着Kubernetes和Seccomp规则的发展,默认配置会不断更新以应对新的安全威胁。禁用默认配置可能意味着错过这些自动更新,增加手动维护成本和安全风险
解决方案(超级简单)
containers:
- image: myImage:v1
imagePullPolicy: Always
securityContext:
seccompProfile: # 重点代码
type: RuntimeDefault # 重点代码
代码实战案例
5. Restrict container from acquiring additional privileges 限制容器获得额外特权
问题详解
- 在 Kubernetes (k8s) 中,"Restrict container from acquiring additional privileges" 是一项安全措施,旨在限制容器运行时获得超出其初始分配权限的能力。这主要是通过 Pod 的安全上下文中的 securityContext 配置来实现的,特别是利用 runAsUser、runAsGroup、fsGroup 以及 allowPrivilegeEscalation 等字段。
- allowPrivilegeEscalation: 这是一个布尔值,当设置为 false 时,会阻止容器内的进程提升其自身的权限(例如,从非 root 用户变为 root 用户)。这是直接限制容器获取额外权限的关键设置。
解决方案
securityContext:
allowPrivilegeEscalation: false # 重点代码
6. Mount container's root filesystem as read only 将容器的根文件系统装载为只读
问题详解
- 在 Kubernetes (k8s) 中,将容器的根文件系统挂载为只读是一种提高容器安全性和稳定性的做法。
- 这意味着容器内的进程不能修改其根 (/) 目录下的任何文件或目录。这对于运行无状态服务或者防止恶意修改系统文件特别有用。
解决方案
在 Pod 的 YAML 配置中的容器 securityContext 字段下设置 readOnlyRootFilesystem 为 true
apiVersion: v1
kind: Pod
metadata:
name: example-pod
spec:
containers:
- name: example-container
image: your-container-image
securityContext:
readOnlyRootFilesystem: true # 这里设置了根文件系统为只读
# 其他容器配置...
# 其他 Pod 配置...
注意,如果你的容器需要写入文件(比如日志、临时文件或应用数据),你做了上面的配置以后,会遇到无法写入文件的问题,例如会遇到项目启动报错:Read-only file system,可参考下面的解决方案
7. Read-only file system 问题解决(文件夹只读问题解决:如果你要在readOnlyRootFilesystem: true状态下,读写一个文件夹)
你需要考虑下面两个问题:
- 需要读写权限的是单文件,还是多文件?单个文件需要权限写入以及多个文件(建议使用文件夹来管理)需要获取写入权限,下面会根据不同来进行分析解决
- 这个文件是原来里面有数据,还是没有数据?直接可以写入的?如果是里面有内容,非空文件或者非空文件夹,请直接移步到文章结尾:实战代码(非空目录或者非空文件夹解决Read-only file system)
- 如果是单个文件,初始为空,需要获取权限读写,参考第8即可
方案一:EmptyDir Volume - 动态创建一个临时存储卷,该卷的生命周期与 Pod 相同,但不持久化数据。
volumeMounts:
- mountPath: /var/log # 需要获取读写权限的文件夹
name: temp-log-volume # 自己命名就好
volumes:
- name: temp-log-volume # 自己命名就好
emptyDir: {} # 不需要改动,你就这么抄就行了
代码实战案例
方案二:HostPath Volume - 将主机上的目录挂载到容器内,适用于单节点测试或有特定需求的场景。(很少用这个,一般我们通常都是多节点,所以都不用这个挂载哦~~)
volumeMounts:
- mountPath: /var/data
name: host-data-volume
volumes:
- name: host-data-volume
hostPath:
path: /data/on/host
type: Directory
方案三:PersistentVolumeClaim (PVC)-对于需要持久化存储的数据,使用 PVC 绑定到 PersistentVolume (PV),实现数据的持久化存储。(持久或者重要的数据,一定要使用这个)
创建PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
挂载
volumeMounts:
- mountPath: /var/persistent-data
name: persistent-data-volume
volumes:
- name: persistent-data-volume
persistentVolumeClaim:
claimName: my-pvc
代码实战案例
因此步骤可以展开的有点多,所以可以参考我的其他文章:如何在 Kubernetes (k8s)创建并使用PersistentVolumeClaim (PVC)
方案四:
如果以上都未解决你的问题,请直接参考下文:10. 使用Init Containers初始化你的文件夹或者文件
8. Read-only file system 问题解决(单个文件只读问题解决:如果你要在readOnlyRootFilesystem: true状态下,读写单个文件)
方案一:使用 EmptyDir 实现单个文件可写
- 如果是已经存在的文件,需要设置为可写,我们可以使用Init Containers(初始化容器)把资料搞进去,下面有Init Containers用法=
- 尽管 EmptyDir 提供的是临时存储,但它是可写的,适合存放那些不需持久保存的数据。如果确实只需要单个文件可写,可以通过在 Pod 定义中挂载 EmptyDir 卷并使用 subPath 技巧来模拟单文件写入。
代码
这个我还没试过,最近太忙啦,小伙伴帮我试试看
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
securityContext:
readOnlyRootFilesystem: true
containers:
- name: my-container
image: my-image
volumeMounts:
- name: temp-file-volume
mountPath: /path/to/writable/file # 期望文件可写的位置
subPath: writable.txt # 指定文件名
volumes:
- name: temp-file-volume
emptyDir: {}
方案二:使用软链接(Symbolic Link)
- 此种类型使用起来比较麻烦,大家酌情选择
- 在容器启动脚本中创建一个从挂载的目录到所需文件的软链接。这要求你的应用能够接受通过软链接访问配置文件。但这种方法增加了容器配置的复杂度,并且不如直接使用subPath优雅。
- 在readOnlyRootFilesystem: true的约束下,直接在容器的只读文件系统中创建软链接至可写位置(如外部卷或宿主机目录)是不可行的,因为创建软链接本身也算作是对文件系统的写操作。但是,你可以在构建镜像时预先创建软链接至一个预期会在运行时挂载为可写卷的目录。
代码示例1:在dockerfile中使用软链接,然后打包镜像,再使用新镜像运行yaml
下面是一个nginx镜像部署普通前端代码的dockerfile案例,代码中两个RUN指令向/entrypoint.sh文件末尾追加内容
- 第一条指令将/dev/stdout链接到/opt/nginx/log/nginx/access.log
- 第二条指令将/dev/stderr链接到/opt/nginx/log/nginx/error.log
FROM node:16.14.2-alpine as build
WORKDIR /app
COPY package.json .npmrc ./
RUN npm config set strict-ssl false
RUN npm config set registry https://registry.npmmirror.com && npm install
COPY . ./
RUN npm run lint:fix
RUN npm run build
FROM docker/nginx:1.25.4-bookworm
COPY --from=build /app/dist/ /usr/share/nginx/html
COPY nginx-uat.conf /etc/nginx/conf.d/default.conf
RUN useradd -m xiaojin
RUN sed -i 's|/var/|/opt/nginx/|' /etc/nginx/nginx.conf
RUN echo '#!/bin/sh' > /entrypoint.sh
RUN echo 'mkdir -p /opt/nginx/log/nginx' >> /entrypoint.sh
RUN echo 'mkdir -p /opt/nginx/run' >> /entrypoint.sh
RUN echo 'touch /opt/nginx/log/nginx/error.log' >> /entrypoint.sh
RUN echo 'touch /opt/nginx/log/nginx/access.log' >> /entrypoint.sh
RUN echo 'ln -sf /dev/stdout /opt/nginx/log/nginx/access.log' >> /entrypoint.sh # 关键代码
RUN echo 'ln -sf /dev/stderr /opt/nginx/log/nginx/error.log' >> /entrypoint.sh # 关键代码
RUN echo 'touch /opt/nginx/run/nginx.pid' >> /entrypoint.sh
RUN echo 'chown -R xiaojin:xiaojin /opt/nginx' >> /entrypoint.sh
RUN echo 'exec nginx -g "daemon off;"' >> /entrypoint.sh
RUN chmod +x /entrypoint.sh
CMD ["/entrypoint.sh"]
USER xiaojin
代码示例2:在dockerfile中使用软链接,然后打包镜像,再使用新镜像运行yaml
假设现在项目中有个文件是:/app/readonly/xiaojin.json,但是目前它是只读的,我想要将它改为可写,怎么办呢?我们可以在运行时挂载一个卷到/var/data,并且应用需要写入xiaojin.json,你可以在Dockerfile中这样写代码:
# 在构建镜像时创建软链接
RUN mkdir -p /var/data && \
ln -s /var/data/xiaojin.json /app/readonly/xiaojin.json
尽管容器的根文件系统是只读的,但通过软链接 /app/readonly/xiaojin.json 写入的数据实际上会存储在可写的/var/data/xiaojin.json中
注意事项
- 这种方法要求在构建镜像时明确知道运行时卷的挂载点
- 软链接本身及其目标目录(在本例中为/var/data)必须在容器启动前就存在或被正确挂载,否则软链接将无效或指向不存在的位置
- 安全性考虑:确保软链接不会意外地指向容器中敏感或重要的文件系统部分,特别是当容器的其他部分可能对这些链接有写权限时
方案三:使用 PersistentVolumeClaim (PVC) 实现持久化文件写入
对于需要持久化的文件,应该创建一个 PersistentVolumeClaim (PVC),然后在 Pod 中挂载该 PVC。
代码:创建 PersistentVolumeClaim (PVC) yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteOnce # 可选模式有:ReadWriteOnce、ReadOnlyMany、ReadWriteMany
resources:
requests:
storage: 1Gi # 请求1GiB的存储空间
storageClassName: standard # 如果使用动态卷,则需指定存储类名称
- accessModes: 指定如何访问存储卷,常见的有只读多路(ReadOnlyMany)、读写一次(ReadWriteOnce)、读写多路(ReadWriteMany)。
- resources.requests.storage: 请求的存储空间大小。
- storageClassName: 如果希望 Kubernetes 自动为你创建 PV(使用动态存储供给),则需要指定一个存在的存储类
代码实战案例
部署yaml,此处以Pod为例吧~
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
securityContext:
readOnlyRootFilesystem: true
containers:
- name: my-container
image: my-image
volumeMounts:
- name: persistent-file-volume
mountPath: /path/to/persistent/file
volumes:
- name: persistent-file-volume
persistentVolumeClaim:
claimName: my-pvc
方案四:
如果以上都未解决你的问题,请直接参考下文:10. 使用Init Containers初始化你的文件夹或者文件
9. [spec.jobTemplate.spec.template.spec.volumes[3].configMap.defaultMode: Invalid value: 755: must be a number between 0 and 0777 (octal), both inclusive
问题详解
- 在Unix/Linux系统中,文件或目录的权限通常用一个四位的八进制数来表示,每位分别对应所有者的权限、所属组的权限和其他用户的权限,每一位可以是0到7的数字,分别代表不同的权限组合。
- 0777是八进制表示,对应于十进制的7 * 8^2 + 7 * 8^1 + 7 * 8^0 = 448 + 56 + 7 = 511。
- 511是十进制表示,直接反映了该权限值的数值大小,但没有明确指出它是基于八进制权限系统的表示。
- 在一些用户界面或API响应中,可能会省略八进制前缀0,仅显示十进制值,因此511就是0777的另一种表示方式,代表了所有用户(所有者、所属组、其他人)都有读、写、执行权限。
解决方案
这个一般出现在修改configMap.defaultMode的时候,解决它非常简单:
- yaml文件使用命令行执行,不要使用网页端修改和保存就OK了
- 使用界面化的工具时候,需要将八进制改为十进制
10. 使用Init Containers初始化你的文件夹或者文件~(超简单解决readOnlyRootFilesystem: true情况下项目文件或者文件夹出现Read-only file system或者lock file on a read-only directory:等问题)
问题详解
为什么要考虑到使用Init Containers?
- 当挂载点目录与已存在的目录重名时,挂载的卷(不论是EmptyDir、PersistentVolumeClaim还是ConfigMap等)会覆盖原有的目录内容。这意味着原有的目录及其内容在Pod运行期间将不可见,取而代之的是挂载卷提供的内容。
Kubernetes不可不知的服务执行顺序
在Kubernetes中,Init Containers 会比 应用容器(主容器)中的目录挂载先执行。Init Containers的设计目的就是在任何应用容器启动之前执行初始化任务,包括但不限于预填充数据、设置配置、甚至等待依赖服务就绪。具体流程如下所示:
- Pod调度:Kubernetes调度器找到合适的节点来运行Pod。
- 创建网络接口和存储卷:为Pod创建必要的网络接口和挂载存储卷。
- Init Container执行:按顺序执行Init Containers,每个Init Container必须成功结束,下一个才能开始。在此阶段,应用容器尚未启动,但存储卷(包括EmptyDir)已经挂载。
- 主容器启动:所有Init Containers成功执行完毕后,应用容器开始启动。此时,应用容器会挂载相同的存储卷,可以看到由Init Containers创建或修改的任何文件和目录。
解决方案
如果我配置了/app/xiaojin目录为emptyDir实现可写,如何初始化这个文件夹的内容呢
apiVersion: v1
kind: Pod
metadata:
name: my-app-init-xiaojin
spec:
initContainers: # 初始化容器部分
- name: init-xiaojin-directory
image: busybox:latest # 使用包含基础命令的镜像,如mkdir、touch等
command: ['sh', '-c', "mkdir -p /mnt/xiaojin && echo 'Initial content' > /mnt/xiaojin/initial_file.txt"] # 初始化命令
volumeMounts: # 挂载卷
- name: xiaojin-emptydir # 与下面volumes中定义的卷名称对应
mountPath: /mnt/xiaojin # Init Container中挂载的路径
containers: # 应用容器部分
- name: my-app-container
image: my-app-image
volumeMounts:
- name: xiaojin-emptydir
mountPath: /app/xiaojin # 应用容器中挂载的路径
volumes: # 卷定义
- name: xiaojin-emptydir
emptyDir: {} # 使用emptyDir卷
尽管Init Container直接操作的是/mnt/xiaojin,但所有在/mnt/xiaojin下的更改都会反映到与之共享相同emptyDir卷的/app/xiaojin目录中,通过共享emptyDir卷,Init Container对/mnt/xiaojin的初始化操作等同于间接初始化了/app/xiaojin目录的内容。
实战代码(彻底解决Read-only file system,无需考虑文件类型,统统适用)
在readOnlyRootFilesystem: true情况下,下面代码可以解决的报错示例:
- Attempted to create a lock file on a read-only directory
- readOnlyRootFilesystem: true 后 k8s pvc可读写怎么配置IllegalOperation: Attempted to create a lock file on a read-only directory: /data/db
- /opt/bitnami/logstash/config/jvm.options: Read-only file system
- 在readOnlyRootFilesystem: true情况下,在项目中会有报错Read-only file system,我们以这个文件夹为例:/opt/countly/plugins下有一大堆文件,有的需要写入权限,有的需要修改权限,怎么办呢?
下面是我测试过的代码,在readOnlyRootFilesystem: true报错Read-only file system情况下,我设置了两个文件夹/opt/countly/plugins和/opt/countly/frontend/express/都拥有读写权限:
apiVersion: apps/v1
kind: Deployment
spec:
progressDeadlineSeconds: 600
replicas: 3
revisionHistoryLimit: 10
selector:
matchLabels:
app: xiaojin-test-demo
template:
metadata:
labels:
app: xiaojin-test-demo
namespace: idp-mop
spec:
initContainers: # 初始化容器部分
- name: init-xiaojin-directory
image: xiaojin-test-demo/frontend:23.03:23.03 # 使用包含基础命令的镜像,如mkdir、touch等
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
command: ['sh', '-c', "mkdir -p /mnt/xiaojin && cp -r /opt/countly/plugins/* /mnt/xiaojin/ && cp -r /opt/countly/frontend/express/* /dist/xiaojin/ "] # 初始化命令
volumeMounts:
- name: countly-dir
mountPath: /mnt/xiaojin
readOnly: false
- name: countly-dist
mountPath: /dist/xiaojin
readOnly: false
containers:
- env:
- name: NODE_OPTIONS
value: '--max-old-space-size=2048'
image: xiaojin-test-demo/frontend:23.03:23.03
imagePullPolicy: Always
volumeMounts:
- name: countly-dir
mountPath: /opt/countly/plugins
- name: countly-dist
mountPath: /opt/countly/frontend/express
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
name: xiaojin-test-demo
ports:
- containerPort: 6001
name: 6001tcp
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /ping
port: 6001
scheme: HTTP
initialDelaySeconds: 300
periodSeconds: 30
successThreshold: 1
timeoutSeconds: 30
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
volumes:
- emptyDir: {}
name: countly-dir
- emptyDir: {}
name: countly-dist
结语
- 今天就写到这里啦~
- 小伙伴们,( ̄ω ̄( ̄ω ̄〃 ( ̄ω ̄〃)ゝ我们明天再见啦~~
- 大家要天天开心哦
欢迎大家指出文章需要改正之处~
学无止境,合作共赢