CKS认证 | Day4 最小化微服务漏洞

一、Pod安全上下文

安全上下文(Security Context):K8s对Pod和容器提供的安全机制,可以设置Pod特权和访问控制。
安全上下文限制维度:

  • 自主访问控制(Discretionary Access Control):基于用户ID(UID)和组ID(GID),来判定对对象(例如文件) 的访问权限;
  • 安全性增强的 Linux(SELinux): 为对象赋予安全性标签;
  • privileged:以 特权模式 或者 非特权模式 运行;
  • Linux Capabilities:为进程赋予 root 用户的部分特权而非全部特权;
  • AppArmor:定义Pod使用AppArmor限制容器对资源访问限制;
  • Seccomp:定义Pod使用Seccomp限制容器进程的系统调用;
  • AllowPrivilegeEscalation: 禁止容器中进程(通过 SetUID 或 SetGID 文件模式)获得特权提升。当容器以特权模式 运行或者具有CAP_SYS_ADMIN能力时,AllowPrivilegeEscalation总为True;
  • readOnlyRootFilesystem:以只读方式加载容器的根文件系统;

配置说明:

bash 复制代码
spec:
  securityContext:            # Pod级别的安全上下文,对内部所有容器均有效
    runAsUser <integer>       # 以指定的用户身份运行容器进程,默认由镜像中的USER指定
    runAsGroup <integer>      # 以指定的用户组运行容器进程,默认使用的组随容器运行时
    fsGroup <integer>         # 数据卷挂载后的目录文件设置为该组
    runAsNonRoot <boolean>    # 是否以非root身份运行
    seLinuxOptions <Object>   # SELinux的相关配置
    sysctls <[]Object>        # 应用到当前Pod上的名称空间级别的sysctl参数设置列表
  containers:
  - name: xxx
    image: xxx
    securityContext:           # 容器级别的安全上下文,仅生效于当前容器
      runAsUser <integer>      # 以指定的用户身份运行容器进程
      runAsGroup <integer>     # 以指定的用户组运行容器进程
      runAsNonRoot <boolean>   # 是否以非root身份运行
      allowPrivilegeEscalation <boolean>     # 是否允许特权升级
      capabilities <Object>    # 于当前容器上添加(add)或删除(drop)的内核能力
        add <[]string>         # 添加由列表定义的各内核能力
        drop <[]string>        # 移除由列表定义的各内核能力
      privileged <boolean>     # 是否运行为特权容器
      readOnlyRootFilesystem <boolean>   # 是否将根文件系统设置为只读模式
      seLinuxOptions <Object>  # SELinux的相关配置

1.1 设置容器以普通用户运行

**背景:**容器中的应用程序默认以root账号运行的,这个root与宿主机root账号是相同的, 拥有大部分对Linux内核的系统调用权限,这样是不安全的,所以我们应该将容器以普 通用户运行,减少应用程序对权限的使用。

可以通过两种方法设置普通用户:

1.Dockerfile里使USER指定运行用户

FROM python

RUN useradd python

RUN mkdir /data/www -p

COPY . /data/www

RUN chown -R python /data

RUN pip install flask -i https://mirrors.aliyun.com/pypi/simple/

WORKDIR /data/www

USER python # 指定运行用户(按照构建顺序,之后的命令都将是以普通python用户运行)

CMD python main.py

2.K8s的yaml里指定spec.securityContext.runAsUser,指定容器默认用户UID

spec:

securityContext:

runAsUser: 1000 # 镜像里必须有这个用户UID

fsGroup: 1000 # 数据卷挂载后的目录属组设置为该组

containers:

  • image: lizhenliang/flask-demo:root

name: web

securityContext:

allowPrivilegeEscalation: false # 不允许提权

1.1.1 Dockerfile里使用USER指定运行用户

实验案例:

1)解压压缩包

javascript 复制代码
[root@k8s-master-1-71 ~]# unzip flask-demo.zip
[root@k8s-master-1-71 ~]# cd flask-demo ; ls
Dockerfile  main.py  templates

2)查看Dockerfile文件(通过dockerfile文件编写构建web应用实例)

通过Dockerfile实现Python容器化构建镜像的第一步,使用python官方镜像安装一个flask模块,再将当前的网站程序拷贝到镜像里

javascript 复制代码
[root@k8s-master-1-71 flask-demo]# cat Dockerfile
FROM python              # 引用python官方镜像
RUN useradd python       # 使用默认root账户创建普通用户
RUN mkdir /data/www -p   # 创建网站根目录
COPY . /data/www         # 将当前flask-demo目录下的文件拷贝网站根目录
RUN chown -R python /data   # 设置所属组
RUN pip install flask -i https://mirrors.aliyun.com/pypi/simple/   # 安装flask模块
WORKDIR /data/www        # 设置默认的工作目录
USER python              # 指定运行用户(按照构建顺序,之后的命令都将是以普通python用户运行)
CMD python main.py       # 设置CMD启动该服务作为容器第一个进程

3)查看main.py文件(网站主程序)

python 复制代码
[root@k8s-master-1-71 flask-demo]# cat main.py
from flask import Flask,render_template      # 导入了flask框架、和渲染模板
的函数

app = Flask(__name__)    # 做一个Flask的web应用实例

@app.route('/')    # 定义路由访问首页,映射到对应Index函数中
def index():
    return render_template("index.html")   # Index函数是以渲染后的HTML模板文件内容进行响应

if __name__ == "__main__":
    app.run(host="0.0.0.0",port=8080)  # 监听所有的IP地址及8080端口通信

4)查看templates/index.html文件(网站模板文件)

javascript 复制代码
[root@k8s-master-1-71 flask-demo]# cat templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>

<h1>Hello Python!</h1>

</body>
</html>

5)构建docker镜像,并允许容器

javascript 复制代码
[root@k8s-master-1-71 flask-demo]# docker build -t flask-demo:v1 .
[root@k8s-master-1-71 flask-demo]# docker run -d --name=demo -p 80:8080 flask-demo:v1

# 测试:容器的程序都是用python用户而非root用户运行
[root@k8s-master-1-71 flask-demo]# docker exec -it demo bash
python@577f69610117:/data/www$ ps -ef
UID         PID   PPID  C STIME TTY          TIME CMD
python        1      0  0 13:08 ?        00:00:00 /bin/sh -c python main.py
python        7      1  2 13:08 ?        00:00:01 python main.py
python        8      0  3 13:08 pts/0    00:00:00 bash
python       14      8  0 13:08 pts/0    00:00:00 ps -ef

# 容器与宿主机共享内核,所以在容器启动后,会有一个进程通过普通用户运行
[root@k8s-master-1-71 ~]# ps -ef |grep python
root        984      1  0 20:10 ?        00:00:02 /usr/bin/python2 -Es /usr/sbin/tuned -l -P
support  104099 104079  0 21:08 ?        00:00:00 /bin/sh -c python main.py
support  104205 104099  0 21:08 ?        00:00:01 python main.py
root     108386 107460  0 21:10 pts/1    00:00:00 grep --color=auto python

1.1.2 K8s里指定spec.securityContext.runAsUser,指定容器默认用户UID

实验案例:

1)配置安全上下文

javascript 复制代码
[root@k8s-master-1-71 ~]# kubectl apply -f flask-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: flack-demo
  name: flack-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: flack-demo
  template:
    metadata:
      labels:
        app: flack-demo
    spec:
      securityContext:     # 配置安全上下文
        runAsUser: 1000    # 镜像里必须有这个用户UID
        fsGroup: 1000      # 数据卷挂载后的目录属组设置为该组
      containers:
      - image: lizhenliang/flask-demo:root
        name: flask-demo

# 测试:容器的程序都是用python用户而非root用户运行
[root@k8s-master-1-71 ~]# kubectl get pods
NAME                         READY   STATUS    RESTARTS       AGE
flack-demo-f78575dc8-dlq7b   1/1     Running   0              2m29s

[root@k8s-master-1-71 ~]# kubectl exec -it flack-demo-f78575dc8-dlq7b -- bash
python@flack-demo-f78575dc8-dlq7b:/data/www$ ps -ef
UID         PID   PPID  C STIME TTY          TIME CMD
python        1      0  0 14:00 ?        00:00:00 /bin/sh -c python main.py
python        7      1  0 14:00 ?        00:00:00 python main.py
python        8      0  0 14:01 pts/0    00:00:00 bash
python       14      8  0 14:01 pts/0    00:00:00 ps -ef

注意:如果宿主机已经有1000 用户了,如果构建的docker镜像用的也是1000 可能会有冲突

1.2 k8s里启用特权容器(避免使用)

**背景:**容器中有些应用程序可能需要访问宿主机设备、修改内核等需求,在默认情况下, 容器没这个有这个能力,因此这时会考虑给容器设置特权模式。

启用特权模式:(只能启用于容器级别)

containers:

  • image: busybox

command:

  • sleep

  • 24h

name: web

securityContext:

privileged: true

注意:启用特权模式就意味着要为容器提供了访问Linux内核的所有能力,这是很危险的, 为了减少系统调用的供给,可以使用Capabilities为容器赋予仅所需的能力。

案例:

javascript 复制代码
# 非特权模式下挂载目录(普通Pod容器)
[root@k8s-master-1-71 ~]# kubectl apply -f bs-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: bs-demo
  name: bs-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: bs-demo
  template:
    metadata:
      labels:
        app: bs-demo
    spec:
      containers:
      - image: busybox
        command:
        - sleep
        - 24h
        name: busybox
        
# 测试1:非特权模式下,无法进行mount挂载
[root@k8s-master-1-71 ~]# kubectl exec -it bs-demo-59d897b956-7nrt4 -- sh
/ # mount -t tmpfs /tmp/ /tmp/
mount: permission denied (are you root?)    # 权限拒绝
javascript 复制代码
# 特权模式下挂载目录
[root@k8s-master-1-71 ~]# kubectl apply -f bs-demo-pri.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: bs-demo-pri
  name: bs-demo-pri
spec:
  replicas: 1
  selector:
    matchLabels:
      app: bs-demo-pri
  template:
    metadata:
      labels:
        app: bs-demo-pri
    spec:
      containers:
      - image: busybox
        command:
        - sleep
        - 24h
        name: busybox
        securityContext:        # 添加安全上下文
          privileged: true      # 开启特权模式

# 测试2:特权模式下,可以进行mount挂载
[root@k8s-master-1-71 ~]# kubectl exec -it bs-demo-pri-747c9976f7-jr9fn -- sh
/ # mount -t tmpfs /tmp/ /tmp/
/ #        # 权限通过

1.3 Linux Capabilities

Capabilities 是一个内核级别的权限,它允许对内核调用权 限进行更细粒度的控制,而不是简单地以 root 身份能力授权。

包括更改文件权限、控制网络子系统和执行系统管理等功能。在 securityContext 中,可以添加或删除 Capabilities,做到容器精细化权限控制。

|----------------------|--------------------------------------------|
| Capabilities 名称 | 描述 |
| CAP_AUDIT_CONTROL | 启用和禁用内核审计;改变审计过滤规则;检索审计状态和过滤规则 |
| CAP_AUDIT_READ | 允许通过multicast netlink 套接字读取审计日志 |
| CAP_AUDIT_WRITE | 将记录写入内核审计日志 |
| CAP_BLOCK_SUSPEND | 使用可以阻止系统挂起的特性 |
| CAP_CHOWN | 修改文件所有者的权限 |
| CAP_DAC_OVERRIDE | 忽略文件的 DAC访问限制 |
| CAP_DAC_READ_SEARCH | 忽略文件读及目录搜索的 DAC 访问限制 |
| CAP_FOWNER | 忽略文件属主ID必须和进程用户ID相匹配的限制 |
| CAP_FSETID | 允许设置文件的 setuid 位 |
| CAP_IPC_LOCK | 允许锁定共享内存片段 |
| CAP_IPC_OWNER | 忽略IPC所有权检查 |
| CAP_KILL | 允许对不属于自己的进程发送信号 |
| CAP_LEASE | 允许修改文件锁的FLLEASE标志 |
| CAP_LINUX_IMMUTABLE | 允许修改文件的IMMUTABLE和APPEND 属性标志 |
| CAP_MAC_ADMIN | 允许MAC配置或状态更改 |
| CAP_MAC_OVERRIDE | 覆盖MAC(Mandatory AccessControl) |
| CAP_MKNOD | 允许使用mknod0)系统调用 |
| CAP_NET_ADMIN | 允许执行网络管理任务: 接口、防火墙和路由等 |
| CAP_NET_BIND_SERVICE | 允许绑定到小于1024的端口 |
| CAP_NET_BROADCAST | 允许网络广播和多播访问 |
| CAP_NET_RAW | 允许使用原始套接字 |
| CAP_SETGID | 允许改变进程的 GID |
| CAP SETFCAP | 允许为文件设置任意的capabilities |
| CAP_SETPCAP | 允许向其它进程转移能力以及删除其它进程的任意能力(只限init进程) |
| CAP_SETUID | 允许改变进程的UID |
| CAP_SYS_ADMIN | 允许执行系统管理任务,如加载或卸载文件系统、设置磁盘配额等 |
| CAP_SYS_BOOT | 允许重新启动系统 |
| CAP_SYS_CHROOT | 允许使用chroot()系统调用 |
| CAP_SYS_MODULE | 允许插入和删除内核模块 |
| CAP_SYS_NICE | 允许提升优先级及设置其他进程的优先级 |
| CAP_SYS_PACCT | 允许执行进程的BSD式审计 |
| CAP_SYS_PTRACE | 允许跟踪任何进程 |
| CAP_SYS_RAWIO | 允许直接访问/devport/dev/mem 、/dev/kmem及原始块设备 |
| CAP_SYS_RESOURCE | 忽略资源限制 |
| CAP_SYS_TIME | 允许改变系统时钟 |
| CAP_SYS_TTY_CONFIG | 允许配置TTY 设备 |
| CAP_SYSLOG | 允许使用 syslog0系统调用 |
| CAP_WAKE_ALARM | 允许触发一些能唤醒系统的东西(比如CLOCK BOOTTIME ALARM 计时器) |

通过capsh查看已有绑定的Capabilities:capsh --print

javascript 复制代码
# 系统用户root,查看已有绑定的Capabilities
root@flack-demo-7ccb4cb9f6-ghngb:/data/www# capsh --print
Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+ep
Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
Securebits: 00/0x0/1'b0
 secure-noroot: no (unlocked)
 secure-no-suid-fixup: no (unlocked)
 secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=0(root)
javascript 复制代码
# 普通用户python,查看已有绑定的Capabilities
python@flack-demo-f78575dc8-dlq7b:/data/www$ capsh --print
Current: =
Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
Securebits: 00/0x0/1'b0
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
uid=1000(python)
gid=1000(python)
groups=1000(python)

普通用户相比root用户会少一些Capabilities,只会开放一部分基本的权限控制进行使用。

1.3.1 容器默认没有挂载文件系统能力,添加SYS_ADMIN增加这个能力

由于启用特权模式给容器提供访问Linux内核的所有能力, 为了避免危险性,使用Capabilities为容器赋予仅所需的部分root能力(细粒度控制)。

spec:

containers:

  • image: busybox

name: test

command:

  • sleep

  • 24h

securityContext:

capabilities:

add: ["SYS_ADMIN"]

案例:

javascript 复制代码
[root@k8s-master-1-71 ~]# kubectl apply -f bs-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: bs-demo
  name: bs-demo-cap
spec:
  replicas: 1
  selector:
    matchLabels:
      app: bs-demo
  template:
    metadata:
      labels:
        app: bs-demo
    spec:
      containers:
      - image: busybox
        command:
        - sleep
        - 24h
        name: busybox
        securityContext:           # 添加安全上下文
          capabilities:            # 添加capabilities
            add: ["SYS_ADMIN"]     # 添加"SYS_ADMIN"权限


# 测试:# 权限通过,可以进行mount挂载
[root@k8s-master-1-71 ~]# kubectl exec -it bs-demo-cap-6646c8d695-9879c -- sh
/ # mount -t tmpfs /tmp/ /tmp/

add 是添加权限,drop 是删除权限

1.3.2 只读挂载容器文件系统,防止恶意二进制文件创建

spec:

containers:

  • image: busybox

name: test

command:

  • sleep

  • 24h

securityContext:

readOnlyRootFilesystem: true

案例:

javascript 复制代码
[root@k8s-master-1-71 ~]# kubectl apply -f bs-demo-2.yaml
kind: Deployment
metadata:
  labels:
    app: bs-demo
  name: bs-demo-read
spec:
  replicas: 1
  selector:
    matchLabels:
      app: bs-demo
  template:
    metadata:
      labels:
        app: bs-demo
    spec:
      containers:
      - image: busybox
        command:
        - sleep
        - 24h
        name: busybox
        securityContext:                   # 添加安全上下文
          readOnlyRootFilesystem: true     # 开启只读挂载容器文件系统功能


# 测试:开启只读系统,创建文件失败
[root@k8s-master-1-71 ~]# kubectl exec -it bs-demo-read-564bb8c497-vhjqh -- sh
/ # touch aa
touch: aa: Read-only file system

二、Pod安全策略(PSP)

PodSecurityPolicy(简称PSP)Pod 安全策略,是集群级别的资源,是Kubernetes中Pod部署时重要的安全校验手段,它首先定义角色,规定了对 Pod 行为的限制,其中包括对特 权容器、主机网络、Capability、加载卷类型等内容进行了限制,然后通过 RBAC 把 SA-Pod-PSP 三者结合起来,完成对 Pod 权限的限制。注意:PSP 在 1.21 废弃,1.25 版本删除。

它能够 有效地约束应用运行时行为安全。 使用PSP对象定义一组Pod在运行时必须遵循的条件及相关字段的默认值,只有Pod满足这 些条件才会被K8s接受。

Pod 安全策略由设置和策略组成,它们能够控制 Pod 访问的安全特征。这些设置分为如下三类:

  • 基于布尔值控制 :这种类型的字段默认为最严格限制的值。
  • 基于被允许的值集合控制 :这种类型的字段会与这组值进行对比,以确认值被允许。
  • 基于策略控制 :设置项通过一种策略提供的机制来生成该值,这种机制能够确保指定的值落在被允 许的这组值中。

Pod安全策略限制维度:

1、RunAsUser

  • 1)MustRunAs - 必须配置一个 range。使用该范围内的第一个值作为默认值。验证是否不在配置 的该范围内。
  • 2)MustRunAsNonRoot - 要求提交的 Pod 具有非零 runAsUser 值,或在镜像中定义 了 USER 环境变量。不提供默认值。
  • 3)RunAsAny - 没有提供默认值。允许指定任何 runAsUser 。

2、SELinux

  • 1)MustRunAs - 如果没有使用预分配的值,必须配置 seLinuxOptions。默认使 用 seLinuxOptions验证seLinuxOptions。
  • 2)RunAsAny - 没有提供默认值。允许任意指定的 seLinuxOptions ID。

3、SupplementalGroups

  • 1)MustRunAs - 至少需要指定一个范围。默认使用第一个范围的最小值。验证所有范围的值。
  • 2)RunAsAny - 没有提供默认值。允许任意指定的 supplementalGroups ID。

4、FSGroup

  • 1)MustRunAs - 至少需要指定一个范围。默认使用第一个范围的最小值。验证在第一个范围内的 第一个 ID。
  • 2)RunAsAny - 没有提供默认值。允许任意指定的 fsGroup ID。

注意:Pod安全策略实现为一个准入控制器,默认没有启用,当启用后会强制实施 Pod安全策略,没有满足的Pod将无法创建。因此,建议在启用PSP之前先添加 策略并对其授权。

启用Pod安全策略:

javascript 复制代码
vi /etc/kubernetes/manifests/kube-apiserver.yaml
...
- --enable-admission-plugins=NodeRestriction,PodSecurityPolicy
...
systemctl restart kubelet

用户使用SA (ServiceAccount)创建了一个Pod,K8s会先验证这个SA是否 可以访问PSP资源权限,如果可以进一步验证Pod配置是否满足PSP规则,任 意一步不满足都会拒绝部署。 因此,需要实施需要有这几步骤:

  1. 创建SA服务账号
  2. 该SA需要具备创建对应资源权限,例如创建Pod、Deployment
  3. SA使用PSP资源权限:创建Role,使用PSP资源权限,再将SA绑定Role

PSP工作流程:

补充:PSP不足与状况

① 将再1.21版本弃用PSP,在1.25版本删除PSP;

② 仅支持Pod策略,不支持Deployment等;

③ 使用复杂,权限模型存在缺陷,控制不明确;

弃用文章:PodSecurityPolicy Deprecation: Past, Present, and Future | Kubernetes

替代提案:https://github.com/kubernetes/enhancements/issues/2579

2.1 示例1:禁止创建特权模式的Pod

javascript 复制代码
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: psp-example
spec:
  privileged: false    # 不允许特权Pod
  # 下面是一些必要的字段
  seLinux:
    rule: RunAsAny
  supplementalGroups:
    rule: RunAsAny
  runAsUser:
    rule: RunAsAny 
  fsGroup:
    rule: RunAsAny
  volumes:
  - '*'
bash 复制代码
# 创建SA
kubectl create serviceaccount aliang

# 将SA绑定到系统内置Role
kubectl create rolebinding aliang --clusterrole=edit --serviceaccount=default:aliang

# 创建使用PSP权限的Role
kubectl create role psp:unprivileged --verb=use --resource=podsecuritypolicy --resource-name=psp-example

# 将SA绑定到Role
kubectl create rolebinding aliang:psp:unprivileged --role=psp:unprivileged --serviceaccount=default:aliang

2.2 示例2:禁止没指定普通用户运行的容器(runAsUser)

javascript 复制代码
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: psp-example
spec:
  privileged: false   # 不允许特权Pod
  # 下面是一些必要的字段
  seLinux:
    rule: RunAsAny
  supplementalGroups:
    rule: RunAsAny
  runAsUser:
    rule: MustRunAsNonRoot     //是否以非root身份运行
  fsGroup:
    rule: RunAsAny
  volumes:
  - '*'

三、OPA Gatekeeper

OPA(Open Policy Agent):是一个开源的、通用策略引擎,可以将策略编写为代码。提供一种高级声明性语言-Rego来编写策略,并把决策这一步骤从复杂的业务逻辑中解耦出来。

OPA可以用来做什么?

  • 拒绝不符合条件的YAML部署

  • 允许使用哪些仓库中的镜像

  • 允许在哪个时间段访问系统

  • 等...

Gatekeeper 是基于 OPA的一个 Kubernetes 策略解决方案,可替代PSP或者部分RBAC功能。

• OPA官网:Open Policy Agent - Homepage | Open Policy Agent

• Gatekeeper项目:https://github.com/open-policy-agent/gatekeeper

• Gatekeeper文档:How to use Gatekeeper | Gatekeeper

当在集群中部署了Gatekeeper组件,APIServer所有的创建、更新或者删除操作都会触发 Gatekeeper来处理,如果不满足策略则拒绝。

工作流程图:

部署Gatekeeper:kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/deploy/gatekeeper.yaml

javascript 复制代码
# 查看相关信息
[root@k8s-master-1-71 ~]# kubectl get pods -n gatekeeper-system
NAME                                             READY   STATUS    RESTARTS       AGE
gatekeeper-audit-7fb8f4c95c-fvmwc                1/1     Running   2 (2m6s ago)   6m8s
gatekeeper-controller-manager-7db564f9f4-j49g5   1/1     Running   0              6m8s
gatekeeper-controller-manager-7db564f9f4-rmq9d   1/1     Running   0              6m8s
gatekeeper-controller-manager-7db564f9f4-sxmzz   1/1     Running   0              6m8s

[root@k8s-master-1-71 ~]# kubectl get svc -n gatekeeper-system
NAME                         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
gatekeeper-webhook-service   ClusterIP   10.111.124.211   <none>        443/TCP   5m33s

[root@k8s-master-1-71 ~]# kubectl get sa -n gatekeeper-system
NAME               SECRETS   AGE
default            0         5m52s
gatekeeper-admin   0         5m51s

Gatekeeper的策略由两个资源对象组成:

  1. Template:策略逻辑实现的地方,使用rego语言(具体对资源做什么操作)
  2. Contsraint:负责Kubernetes资源对象的过滤或者为Template提供输入参数(过滤哪些资源)

补充:先部署模板,再部署约束

3.1 案例1:禁止容器启用特权

1)模板:

查看资源命令 :kubectl get ConstraintTemplate

javascript 复制代码
[root@k8s-master-1-71 ~]# kubectl apply -f test-ConstraintTemplate.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: privileged 
spec:
  crd:
    spec:
      names:
        kind: privileged
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package admission    # 导入准入控制包
        violation[{"msg": msg}] {     # 如果violation为true(表达式通过)说明违反约束
          containers = input.review.object.spec.template.spec.containers
          c_name := containers[0].name    # 获取第一个容器的名称
          containers[0].securityContext.privileged      # 检查是否有启用特权模式。如果返回true,说明违反约束
          msg := sprintf("提示:'%v'容器禁止启用特权!",[c_name])
        }

解释:violation为回调函数,当violation表达式返回值为true,说明违反约束;相反,当violation表达式返回值为false,说明符合约束;

判断表达式:检查是否有启用特权模式。如果返回true,说明违反约束

2)约束:

查看资源 命令 :kubectl get constraints

javascript 复制代码
[root@k8s-master-1-71 ~]# kubectl apply -f test-constraints.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: privileged
metadata:
  name: privileged
spec:
  match:   # 匹配的资源
    kinds:
      - apiGroups: ["apps"]
        kinds:
        - "Deployment"
        - "DaemonSet"
        - "StatefulSet"

测试:

javascript 复制代码
# 查看 ConstraintTemplate 模板
[root@k8s-master-1-71 ~]# kubectl get ConstraintTemplate
NAME         AGE
privileged   103s
# 查看 constraints 约束
[root@k8s-master-1-71 ~]# kubectl get constraints
NAME         ENFORCEMENT-ACTION   TOTAL-VIOLATIONS
privileged                        2

--------------------------------------------------------------------------------
# 部署启用特权模式的Pod
[root@k8s-master-1-71 ~]# kubectl apply -f bs-demo-pri.yaml
Error from server (Forbidden): error when creating "bs-demo-pri.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [privileged] 提示:'busybox'容器禁止启用特权!

删除模板/约束命令:

  • kubectl delete constraints <约束名>
  • kubectl delete ConstraintTemplate <模板名>

3.2 案例2:只允许使用特定的镜像仓库

1)模板:

javascript 复制代码
[root@k8s-master-1-71 ~]# kubectl apply -f test1-ConstraintTemplate.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: image-check
spec:
  crd:
    spec:
      names:
        kind: image-check
      validation:
        openAPIV3Schema: 
          properties:     # 需要满足条件的参数
            prefix:
              type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package image
        violation[{"msg": msg}] { 
          containers = input.review.object.spec.template.spec.containers
          image := containers[0].image
          not startswith(image, input.parameters.prefix)     # 镜像地址开头不匹配并取反则为true,说明违反约束
          msg := sprintf("提示:'%v'镜像地址不在可信任仓库!", [image])
        }

解释:violation为回调函数,当violation表达式返回值为true,说明违反约束;相反,当violation表达式返回值为false,说明符合约束

判断表达式:镜像地址不匹配以"lizhenliang/"开头的镜像,返回true,说明违反约束

2)约束:

javascript 复制代码
[root@k8s-master-1-71 ~]# kubectl apply -f test1-constraints.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: image-check
metadata:
  name: image-check
spec:
  match:
    kinds:
      - apiGroups: ["apps"] 
        kinds:
        - "Deployment"
        - "DaemonSet"
        - "StatefulSet"
  parameters:          # 传递给opa的参数(允许的镜像仓库前缀)
    prefix: "lizhenliang/"

测试:

javascript 复制代码
# 查看 ConstraintTemplate 模板
[root@k8s-master-1-71 ~]# kubectl get ConstraintTemplate
NAME          AGE
image-check   5m26s
# 查看 constraints 约束
[root@k8s-master-1-71 ~]# kubectl get constraints
NAME          ENFORCEMENT-ACTION   TOTAL-VIOLATIONS
image-check

--------------------------------------------------------------------------------
# 部署Pod指定非lizhenliang/开头的镜像,报错:镜像地址不在可信任仓库!
[root@k8s-master-1-71 ~]# kubectl create deployment web --image=nginx
error: failed to create deployment: admission webhook "validation.gatekeeper.sh" denied the request: [image-check] 提示:'nginx'镜像地址不在可信任仓库!
[root@k8s-master-1-71 ~]# kubectl create deployment web --image=192.168.1.10/nginx:v1
error: failed to create deployment: admission webhook "validation.gatekeeper.sh" denied the request: [image-check] 提示:'192.168.1.10/nginx:v1'镜像地址不在可信任仓库!

# 部署Pod指定lizhenliang/开头的镜像,成功创建
[root@k8s-master-1-71 ~]# kubectl create deployment web --image=lizhenliang/java-demo
deployment.apps/web created

四、Secret存储敏感数据

Secret是一个用于存储敏感数据的资源,所有的数据要经过base64编码,数据实际会存储在K8s中Etcd, 然后通过创建Pod时引用该数据。**应用场景:**凭据

1、Pod使用secret数据有两种方式:

  • 变量注入
  • 数据卷挂载

2、kubectl create secret 支持三种数据类型:

**1)docker-registry:**存储镜像仓库认证信息(镜像仓库需要账户密码认证)

**2)generic:**存储用户名、密码:

  • 例如:kubectl create secret generic ssh-key-secret --from-file=ssh-privatekey=/path/to/.ssh/id_rsa --from-file=ssh-publickey=/path/to/.ssh/id_rsa.pub
  • 例如:kubectl create secret generic my-secret --from-literal=username=produser --from-literal=password=123456

**3)tls:**存储证书:kubectl create secret tls --cert=path/to/tls.cert --key=path/to/tls.key

  • 例如 使用ingress - tls 部署https

3、相关命令:

查看Secret: kubectl get secret
相关base64命令:

bash 复制代码
# echo -n 'admin' | base64           //加密(YWRtaW4=)
# echo YWRtaW4= | base64 -d   //解密(admin)

补充: 在Secret 配置文件中未作显式设定时,默认的 Secret 类型是 Opaque

命令示例:

javascript 复制代码
[root@k8s-master ~]# kubectl create secret generic mysql-secret --from-literal=username=admin --from-literal=password=123456

示例:将Mysql用户密码保存到Secret中存储

TYPE类型参考:Secrets | Kubernetes

javascript 复制代码
[root@k8s-master-1-71 ~]# kubectl apply -f mysql-secret-pod.yaml
apiVersion: v1
kind: Secret
metadata:
  name: mysql-secret
type: Opaque    # 默认的 Secret 类型是 Opaque
data:
  mysql-root-password: "MTIzNDU2"

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: db
        image: mysql:5.7.30
        env:
        - name: MYSQL_ROOT_PASSWORD     # 为Mysql预先定义变量
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: mysql-root-password

测试:

javascript 复制代码
[root@k8s-master-1-71 ~]# kubectl get pods
NAME                        READY   STATUS    RESTARTS      AGE
mysql-f958d8ddb-k69cp       1/1     Running   0             74s

[root@k8s-master-1-71 ~]# kubectl exec -it mysql-f958d8ddb-k69cp -- bash
root@mysql-f958d8ddb-k69cp:/# env |grep PASSWORD
MYSQL_ROOT_PASSWORD=123456

补充:容器镜像对变量的使用方式:

① 容器里的程序读取系统变量(例如Python的OS模块)

② 容器启动后脚本(entrypoint)获取变量

当构建docker镜像时,配置entrypoint之后,CMD将作为这个执行脚本的参数传递给它,没有配置entrypoint则默认允许CMD执行的程序

bash 复制代码
# docker run -d nginx ls

五、安全沙箱运行容器

所知,容器的应用程序可以直接访问Linux内核的系统调用,容器在安全隔离上还是比较弱,虽然 内核在不断地增强自身的安全特性,但由于内核自身代码极端复杂,CVE 漏洞层出不穷。 所以要想减少这方面安全风险,就是做好安全隔离,阻断容器内程序对物理机内核的依赖。

Google开源的一种gVisor容器沙箱技术就是采用这种思路,gVisor隔离容器内应用和内核之间访 问,提供了大部分Linux内核的系统调用,巧妙的将容器内进程的系统调用转化为对gVisor的访问。gVisor兼容OCI,与Docker和K8s无缝集成,很方便使用。

项目地址:https://github.com/google/gvisor

--- 容器中应用程序直接与Linux内核交互(未使用gVisor)

--- 容器中应用程序需要通过gVisor才能使用资源(使用gVisor)

补充:类似安全防火墙,做一层过滤,对不安全的系统调用进行隔离

缺点:安全性提升,但性能性下降5%左右

gVisor架构图:

gVisor 由 3 个组件构成:

  • Runsc 是一种 Runtime 引擎,负责容器的创建与销毁;

  • Sentry 负责容器内程序的系统调用处理;

  • Gofer 负责文件系统的操作代理,IO 请求都会由它转接 到 Host 上;

5.2 gVisor与Docker集成(在k8s-node2测试)

gVisor内核要求:Linux 3.17+

注意:如果用的是CentOS7则需要升级内核,Ubuntu不需要。

已测试过可兼容的应用和工具:https://gvisor.dev/docs/user_guide/compatibility/

bash 复制代码
# 查看有哪些内核版本可供安装
yum  --disablerepo="*"  --enablerepo="elrepo-kernel"  list  available

# 载入ELRepo仓库的公共密钥
rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
# 安装ELRepo仓库的yum源
rpm -Uvh http://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm
yum install -y https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm

# 长期维护版本kernel-lt  如需更新最新稳定版选择kernel-ml(较为激进)
yum --enablerepo=elrepo-kernel install kernel-ml-devel kernel-ml --y

# 查看可用内核版本及启动顺序
awk -F\' '$1=="menuentry " {print i++ " : " $2}' /boot/grub2/grub.cfg

# 设置内核默认启动顺序
grub2-set-default 0

# 运行grub2-mkconfig命令来重新创建内核配置
grub2-mkconfig -o /boot/grub2/grub.cfg

# 重启并查看内核版本
reboot
uname -r

# 查看系统中已安装的内核
rpm -qa | grep kernel

1)准备gVisor二进制文件

参考:https://gvisor.dev/docs/user_guide/install/

要手动下载并安装最新版本,请执行以下步骤:

javascript 复制代码
[root@k8s-node2-1-73 ~]# (
  set -e
  ARCH=$(uname -m)
  URL=https://storage.googleapis.com/gvisor/releases/release/latest/${ARCH}
  wget ${URL}/runsc ${URL}/runsc.sha512 \
    ${URL}/containerd-shim-runsc-v1 ${URL}/containerd-shim-runsc-v1.sha512
  sha512sum -c runsc.sha512 \
    -c containerd-shim-runsc-v1.sha512
  rm -f *.sha512
  chmod a+rx runsc containerd-shim-runsc-v1
  sudo mv runsc containerd-shim-runsc-v1 /usr/local/bin
)

注意runsc:复制到所有用户都可读和可执行的位置很重要,因为runsc它会以用户身份执行nobody,以避免不必要的特权。该/usr/local/bin目录是放置runsc二进制文件的好地方。

2)Docker配置使用gVisor

javascript 复制代码
[root@k8s-node2-1-73 ~]#  /usr/local/bin/runsc install
2023/06/02 22:08:08 Clobber is set. Overwriting runtime runsc not found: adding
2023/06/02 22:08:08 Successfully updated config.

# 重启docker生效
[root@k8s-node2-1-73 ~]# systemctl restart docker

#
 查看docker信息
[root@k8s-node2-1-73 ~]# docker info
...
 Runtimes: io.containerd.runc.v2 runc runsc    # 以安装runsc,但默认运行时是runc
 Default Runtime: runc
...

3)使用runsc运行容器:

javascript 复制代码
[root@k8s-node2-1-73 ~]# docker run -d --runtime=runsc nginx

验证1:dmesg

javascript 复制代码
# 有gVisor接管的容器,可用dmesg查看
[root@k8s-node2-1-73 ~]# docker exec -it e59e011d4cc9 bash
root@e59e011d4cc9:/# dmesg
[    0.000000] Starting gVisor...
[    0.584074] Committing treasure map to memory...
[    0.754707] Creating bureaucratic processes...
[    1.175149] Reading process obituaries...
[    1.291416] Granting licence to kill(2)...
[    1.701557] Constructing home...
[    1.831904] Verifying that no non-zero bytes made their way into /dev/zero...
[    2.264950] Searching for needles in stacks...
[    2.715440] Searching for socket adapter...
[    3.169122] Letting the watchdogs out...
[    3.480816] Segmenting fault lines...
[    3.506517] Setting up VFS...
[    3.783351] Setting up FUSE...
[    3.900146] Ready!

# 没有gVisor接管的容器,dmesg查看
[root@k8s-node2-1-73 ~]# docker exec -it ac1662642021a bash
root@ac1662642021:/# dmesg
dmesg: read kernel buffer failed: Operation not permitted

验证2:uname -r

javascript 复制代码
root@e59e011d4cc9:/# uname -r
4.4.0

root@ac1662642021:/# uname -r
6.3.5-1.el7.elrepo.x86_64

由于容器共享内核,所以查看内核的版本不一样,而使用gVisor接管的容器,所有的系统调用都需要它进行过滤,所以不是宿主机的内核版本

5.3 gVisor与Containerd集成

切换Containerd容器引擎

1)准备配置

bash 复制代码
cat > /etc/sysctl.d/99-kubernetes-cri.conf << EOF
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
sysctl -system

2)安装 containerd(可忽略,安装docker既包含了containerd)

bash 复制代码
cd /etc/yum.repos.d
wget http://mirrors.aliyun.com/dockerce/linux/centos/docker-ce.repo
yum install -y containerd.io

3)修改配置文件

  • pause镜像地址
  • Cgroup驱动改为systemd
  • 增加 runsc容器运行时
  • 配置 docker镜像加速器
javascript 复制代码
# 如果是没有安装docker,自行安装的containerd,需手动创建并修改配置文件
mkdir -p /etc/containerd    # 创建containerd目录(没有则创建)
# 生成并覆盖原有的配置文件
containerd config default > /etc/containerd/config.toml

# 修改配置文件
vi /etc/containerd/config.toml
disabled_plugins = []    # 注释掉,containerd可以作为独立的容器运行;

...
  [plugins."io.containerd.grpc.v1.cri"] 
   sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.2"   # 修改pause镜像地址



   ...
        [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
        ...
          [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
            ...
            SystemdCgroup = true   # Cgroup驱动改为systemd


        [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc]  # 添加.containerd.runtimes.runsc
          runtime_type = "io.containerd.runsc.v1"
        ...
    [plugins."io.containerd.grpc.v1.cri".registry]
    ...
      [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]    # 配置docker镜像加速器
        endpoint = ["https://b9pmyelo.mirror.aliyuncs.com"]

    ...

# 重启容器
systemctl restart containerd

disabled_plugins = ["cri"] //运行模式,如果是禁用containerd的cri,就相当于以docker集成的方式工作,如果注释掉,containerd可以作为独立的容器运行;

4)配置kubelet使用containerd

将 unix:///var/run/cri-dockerd.sock 修改成 unix:///run/containerd/containerd.sock

修改位置:/etc/sysconfig/kubelet 和 /var/lib/kubelet/kubeadm-flags.env

javascript 复制代码
# 方式1:(推荐)
vi /var/lib/kubelet/kubeadm-flags.env
KUBELET_KUBEADM_ARGS="--container-runtime-endpoint=unix:///run/containerd/containerd.sock --pod-infra-container-image=registry.aliyuncs.com/google_containers/pause:3.9"

# 方式2:
vi /etc/sysconfig/kubelet
KUBELET_EXTRA_ARGS="--container-runtime=remote --container-runtime-endpoint=unix:///run/containerd/containerd.sock --cgroup-driver=systemd"

# 重启kubelet服务
systemctl restart kubelet

5)验证

javascript 复制代码
# 重启服务前
[root@k8s-node2-1-73 ~]# kubectl get node -o wide
NAME              STATUS   ROLES           AGE    VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE                                                                                                                              KERNEL-VERSION              CONTAINER-RUNTIME
k8s-master-1-71   Ready    control-plane   106d   v1.26.3   192.168.1.71   <none>        CentOS Linux                                                                                                               7 (Core)   3.10.0-1160.el7.x86_64      docker://23.0.1
k8s-node1-1-72    Ready    <none>          106d   v1.26.3   192.168.1.72   <none>        CentOS Linux                                                                                                               7 (Core)   3.10.0-1160.el7.x86_64      docker://23.0.1
k8s-node2-1-73    Ready    <none>          106d   v1.26.3   192.168.1.73   <none>        CentOS Linux                                                                                                               7 (Core)   6.3.5-1.el7.elrepo.x86_64   docker://23.0.1

# 重启服务后
[root@k8s-node2-1-73 ~]# kubectl get node -o wide
NAME              STATUS   ROLES           AGE    VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE                KERNEL-VERSION              CONTAINER-RUNTIME
k8s-master-1-71   Ready    control-plane   106d   v1.26.3   192.168.1.71   <none>        CentOS Linux 7 (Core)   3.10.0-1160.el7.x86_64      docker://23.0.1
k8s-node1-1-72    Ready    <none>          106d   v1.26.3   192.168.1.72   <none>        CentOS Linux 7 (Core)   3.10.0-1160.el7.x86_64      docker://23.0.1
k8s-node2-1-73    Ready    <none>          106d   v1.26.3   192.168.1.73   <none>        CentOS Linux 7 (Core)   6.3.5-1.el7.elrepo.x86_64   containerd://1.6.16
    # 镜像为containerd

containerd 也有 ctr 管理工具,但功能比较简单,一般使用 crictl工具 检查和调试容器。

项目地址:https://github.com/kubernetes-sigs/cri-tools/

准备crictl 连接containerd 配置文件:

bash 复制代码
cat > /etc/crictl.yaml << EOF
runtime-endpoint: unix:///run/containerd/containerd.sock
EOF

下面是docker与crictl命令对照表:

5.4 K8s使用gVisor运行容器

RuntimeClass 是一个用于选择容器运行时配置的特性,容器运行时配置用 于运行 Pod 中的容器

1)创建RuntimeClass:

查看RuntimeClass:kubectl get runtimeclass

javascript 复制代码
[root@k8s-master-1-71 ~]# kubectl apply -f test-runtimeclass.yaml
apiVersion: node.k8s.io/v1   # RuntimeClass 定义于 node.k8s.io API 组
kind: RuntimeClass
metadata:
  name: gvisor   # 用来引用 RuntimeClass 的名字
handler: runsc   # 对应的 CRI 配置的名称

--------------------------------------------------------------------------------
[root@k8s-master-1-71 ~]# kubectl get runtimeclass
NAME     HANDLER   AGE
gvisor   runsc     9s

注意:对应的 CRI 配置的名称,必须是/etc/containerd/config.toml配置文件中定义容器运行时添加的runsc对应,[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc]

2)创建Pod指定runtimeclass类

补充:由于只在node2切换了containerd,所以需要将Pod部署在node2节点上

javascript 复制代码
[root@k8s-master-1-71 ~]# kubectl apply -f test-rclass-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx-gvisor
spec:
  runtimeClassName: gvisor
  containers:
  - name: nginx
    image: nginx

3)测试gVisor

javascript 复制代码
[root@k8s-master-1-71 ~]# kubectl get pod nginx-gvisor -o wide
NAME           READY   STATUS    RESTARTS   AGE    IP              NODE             NOMINATED NODE   READINESS GATES
nginx-gvisor   1/1     Running   0          110s   10.244.114.38   k8s-node2-1-73   <none>           <none>

[root@k8s-master-1-71 ~]# kubectl exec -it nginx-gvisor -- dmesg
[    0.000000] Starting gVisor...
[    0.139631] Checking naughty and nice process list...
[    0.438644] Recruiting cron-ies...
[    0.852565] Committing treasure map to memory...
[    0.940108] Daemonizing children...
[    1.112831] Reading process obituaries...
[    1.572130] Mounting deweydecimalfs...
[    1.585922] Waiting for children...
[    1.784469] Digging up root...
[    1.904385] Reticulating splines...
[    2.203931] Letting the watchdogs out...
[    2.347858] Setting up VFS...
[    2.657248] Setting up FUSE...
[    3.124126] Ready!

总结:

  • 追求安全性,可以以gVisor启动Pod
  • 追求性能,建议还是默认启动Pod

课后作业

1、 使用containerd作为容器运行时,准备好gVisor,创建一个RuntimeClass, 创建一个Pod在gVisor上运行

小结

本篇为 【Kubernetes CKS认证 DAY4】的开篇学习笔记,希望这篇笔记可以让您初步了解到 如何设置容器以普通用户运行、Pod安全策略(PSP)、OPA Gatekeeper、Secret存储敏感数据、安全沙箱运行容器,不妨跟着我的笔记步伐亲自实践一下吧!


Tip:毕竟两个人的智慧大于一个人的智慧,如果你不理解本章节的内容或需要相关笔记、视频,可私信小安,请不要害羞和回避,可以向他人请教,花点时间直到你真正的理解。

相关推荐
牧天白衣.1 小时前
Docker相关内容
docker·容器·eureka
2401_836836592 小时前
k8s配置管理
云原生·容器·kubernetes
一切顺势而行2 小时前
k8s 使用docker 安装教程
docker·容器·kubernetes
霖檬ing2 小时前
K8s——配置管理(1)
java·贪心算法·kubernetes
澜兮子2 小时前
k8s-服务发布基础
云原生·容器·kubernetes
Andy杨2 小时前
20250707-2-第二章:Kubernetes 核心概念-K8s集群架构,生产部署K8s两_笔记
docker·容器
2401_836836592 小时前
k8s服务发布进阶
云原生·容器·kubernetes
Bruce_Liuxiaowei2 小时前
安全分析:Zabbix 路径探测请求解析
安全·网络安全·zabbix
weixin_472339463 小时前
网络安全核心技术解析:权限提升(Privilege Escalation)攻防全景
安全·web安全