k8s_day03_01

image-20211203083942453

docker-daemon 被拆分了好几个部分

  1. containerd: 高级容器运行时 :为用户使用更方便, 离用户更近、比较易用的命令行容器的管理工具但是包括docker bulid 之类的镜像管理工具【docker 当年捐献出来的只有容器运行时环境,而不包括镜像打包工具】
  2. runc: 低级容器运行时环境:
  3. containerd-shim, 是为了让runc 与oci 兼容的“垫片” 就是中间层。对接k8s CRI 接口,就是从containerd-shim 开始,而不是contained

podman 的好处:相比较docker ,docker 运行容器需要daemon ,而podman 不需要。podman的底层也是复用的runc, 所以podman当年docker 的技术: 当年docker 用的不是runc , 是lxc , 后来换成Libcontainer第三代才是containerd

image-20211203084108436

​ pod 是容器集,是调度的最小单元。pod内部容器共享内部底层容器pause的3个名称空间 , 分别是network、ipc、UTS;pause的主要作用就是提供共享存储卷和名称空间,事实上也可以让pod 内容器共享pid 名称空间【默认是是不共享的,也可以打开pid共享,意味着pod内多个容器进程id pid 是共同编号的】,但是mount、 user 名称空间一般是让它门隔离的, 只是不会让他们共享,而不是不能

api版本的演进 。版本一共可以分成3个: alpha、beta、stable

例子: a

​ lpha 版本可以叫 v1alpha1 表示v1版本的内测阶段。所有的内测版本都不会随着发行版发行的,所以用命令 kubectl api-resources 是找不到alpha 版本的,因为我们作为客户端,能下载可用的话就已经是公开版本了,因此。。。

​ v1alpha1 也可以接着演进为 v1alpha2 直到为beta 版本 v1beta1 等等 最后走向 稳定版本v1 .

apiversion : GROUP/VERSION 。GROUP 可能不会改变,但是 version 可能会随时间变化。所以不同版本的k8 ,同一份配置清单 不一定都能 执行

github 上 Kubernetes v1.23.0-rc.0 rc 是release candidate 发行候选的简写,k8s最少3/4月,最多半年就会发布一个新版本

容器安全上下文

k8s 为了安全的运行容器,专门设计了一种 叫 pod 安全上下文的概念 Security Context 。 Security Context 主要是允许 用户和管理员 定义pod 或容器的特权 或访问控制机制的 。 以配置容器 主机以及其他容器之间的 隔离方式或者称之隔离级别。

​ 人话: pod 安全上下文 就是一组用来决定容器如何创建或运行的约束条件。它们代表创建和运行容器运行时使用的运行时参数

pod 上的SC【Security Context】有2个级别

​ pod 级别:对pod内所有容器生效

​ 容器级别:对pod内的单个容器生效

当然, 如果在集群级别,对所有pod 生效,用PSP: Pod Security Policy

配置格式速览

Security Context 常见配置

apiVersion: v1
kind: Pod
metadata: {…}
spec:
  securityContext:        # Pod级别的安全上下文,对内部所有容器均有效
    runAsUser <integer>   # 以指定的用户身份运行容器进程,默认由镜像中的USER指定
    runAsGroup <integer>   # 以指定的用户组运行容器进程,默认使用的组随容器运行时
    supplementalGroups  <[]integer>  # 为容器中1号进程的用户添加的附加组;
    fsGroup <integer>  # 为容器中的1号进程附加的一个专用组,其功能类似于sgid
    runAsNonRoot <boolean>  # 是否以非root身份运行
    seLinuxOptions <Object>  # SELinux的相关配置
    sysctls  <[]Object>  # 应用到当前Pod上的名称空间级别的sysctl参数设置列表
    windowsOptions <Object>  # Windows容器专用的设置
  containers:
  - name: …
    image: …
    securityContext:       # 容器级别的安全上下文,仅生效于当前容器
      runAsUser <integer>   # 以指定的用户身份运行容器进程
      runAsGroup <integer>   # 以指定的用户组运行容器进程
      runAsNonRoot <boolean>  # 是否以非root身份运行
      allowPrivilegeEscalation <boolean> # 是否允许特权升级
      capabilities <Object>  # 于当前容器上添加(add)或删除(drop)的内核能力
        add  <[]string>  # 添加由列表定义的各内核能力
        drop  <[]string>  # 移除由列表定义的各内核能力
      privileged <boolean>  # 是否运行为特权容器
      procMount <string>   # 设置容器的procMount类型,默认为DefaultProcMount;
      readOnlyRootFilesystem <boolean> # 是否将根文件系统设置为只读模式
      seLinuxOptions <Object>  # SELinux的相关配置
      windowsOptions <Object>  # windows容器专用的设置

capabilities:

​ 在Linux上 ,用户只有2中类型。管理员和普通用户。这样的分类方式太粗糙了。如果让一个普通用户能够管理主机的网络权限 ,但是又不能管理用户,怎么做?除了改sudo 就做不到了。对于 linux 来说 , 所有特权都是内核级别的,而且root 用户也是内核级别的,所以说root 天然具有特权权限,内核级权限 普通用户都没有,Linux就做了这种隔离。为了打破这种魔咒,linux 内核做了一种设定,把内核中的多个权限分成不同的组,分别用于不同类别的管理操作,而这每一个组所涉及的到的操作,我们就把它称为capabilities , 比如管理网络的就叫做CapNetadmin 这个组,管理系统时钟的就叫 CapSystime 如果是拥有系统级管理员权限的就叫CapSysadmin 等等分了好多组 .

​ 在容器中设置capabilities , 就是为了让容器具有 某些名称空间的特权。 add 添加某些内核能力, drop 关闭某些内核能力。 就是说可以让容器拥有内核级特权,管理内核的,但是只给它某些方面的特权而不是所有特权。比如说,正常情况下,普通用户是无法监听1024以内的端口的,如果给普通用户打开了netadmin 权限,用户就能就能监听80端口了。再比如,普通用户是无法管理内核的iptables 规则的,一旦给了他netadmin ,它就可以所在iptables 规则

privileged: 让容器运行为特权模式, 再次提醒特权一般只开放给k8s 系统级别的pod容器,应用容器是不必要或者一定不开启特权的 如 flannel 类型的组件就要开启为特权能力

procMount : 将宿主机的/proc 下系统级别的内核参数直接映射的容器中,,, 这个也不用管

readyonlyRootFilesystem : 设置之后,只能把数据写在存储卷上

管理容器运行身份

例子:

让容器内用户以普通用户身份执行

[root@master01 chapter4]# cat  securitycontext-runasuser-demo.yaml 
# Maintainer: MageEdu <mage@magedu.com>
# # URL: http://www.magedu.com
---
apiVersion: v1
kind: Pod
metadata:
  name: securitycontext-runasuser-demo
  namespace: default
spec:
  containers:
  - name: demo
    image: ikubernetes/demoapp:v1.0
    imagePullPolicy: IfNotPresent
    env:
    - name: PORT
      value: "8080"
    securityContext:
      runAsUser: 1001
      runAsGroup: 1001

验证结果

[root@master01 chapter4]# kubectl exec  securitycontext-runasuser-demo -- ps aux
PID   USER     TIME  COMMAND
    1 1001      0:00 python3 /usr/local/bin/demo.py
   17 1001      0:00 ps aux
[root@master01 chapter4]# 

imagePullPolicy: 镜像拉取策略有3种

  1. never : 从不拉取。本地节点有镜像就用,没有就等着 Pending
  2. IfNotPresent: 如果把pod调度到该节点,如果节点上有镜像,就直接用,没有就拉取
  3. always: 即时本地节点有镜像,也要再次拉取【主要防止节点镜像被恶意攻击?】

imagePullPolicy默认值 取决于镜像的版本 。 如果有明确的版本 ,就是IfNotPresent , 如果是latest ,默认值就是always

管理容器内核功能/特权容器

例子:设定容器级别的capability

linux 内核从2.2 版本 开始支持附加于超级用户的管理权限 ,分割为多个独立的单元,每个单元就是capability.

以下是linux 内核常用的capability:

CAP_CHOWN:改变UID和GID;
CAP_MKNOD:mknod(),创建设备文件;
CAP_NET_ADMIN:网络管理权限;
CAP_SYS_ADMIN:大部分的管理权限; 生产环境给到这个权限就够了,就不要把容器 运行为特权模式了
CAP_SYS_TIME:
CAP_SYS_MODULE:装载卸载内核模块

CAP_NET_BIND_SERVER:允许绑定特权端口【绑定1024以内的】

系统管理员可以直接通过getcap 、setcap 来设定权限

先来一个错误的案例

[root@master01 chapter4]# cat securitycontext-capabilities-demo.yaml 
# Maintainer: MageEdu <mage@magedu.com>
# # URL: http://www.magedu.com
apiVersion: v1
kind: Pod
metadata:
  name: securitycontext-capabilities-demo
  namespace: default
spec:
  containers:
  - name: demo
    image: ikubernetes/demoapp:v1.0
    imagePullPolicy: IfNotPresent
    command: ["/bin/sh","-c"]
    args: ["/sbin/iptables -t nat -A PREROUTING -p tcp --dport 8080 -j REDIRECT --to-port 80 && /usr/bin/python3 /usr/local/bin/demo.py"]
#    securityContext:
#      capabilities:
#        add: ['NET_ADMIN']
#        drop: ['CHOWN']

[root@master01 chapter4]# kubectl  apply -f securitycontext-capabilities-demo.yaml 
pod/securitycontext-capabilities-demo created
[root@master01 chapter4]# kubectl  get po/securitycontext-capabilities-demo
NAME                                READY   STATUS             RESTARTS   AGE
securitycontext-capabilities-demo   0/1     CrashLoopBackOff   1          15s

describe pod 发现以下信息。

[root@master01 chapter4]# kubectl  describe  po/securitycontext-capabilities-demo

image-20211203132922382

image-20211203132945442

查看日志。 结果报错就是操作不允许

[root@master01 chapter4]# kubectl  logs  po/securitycontext-capabilities-demo
getsockopt failed strangely: Operation not permitted

为啥呢? 如果正常状态下 的 容器ps aux ,发现是会以root身份 运行pid =1 的 python3 /usr/local/bin/demo.py

容器内root的uid 也是为0 。问题?

因为容器内的root用户是被虚拟的,虽然看上去是以0号用户在运行,但是容器内的root 是被虚拟的,是映射的宿主机的普通用户而已,而宿主机的普通用户是无法设置iptables的, iptables 是内核级规则

command :

​ 在容器上 ,可以用command 改变 容器中默认要运行的 程序, 就是在制作dockerfile 时可以用CMD或者entrypoint 指明的那个程序。 这里的command 起着类似的作用,前提是comand 所用到的程序 在镜像中必须存在

args: 指向自定义的程序传入参数。 args 其实都写在command 内也行

在正常情况下,容器是不具有特权权限的。因为k8s关闭了特权权限

容器上设置的iptables 和宿主机不一样,不是宿主机的,是容器的名称空间级别的规则

去了注释,加上 NET_ADMIN的capabilities就行了

apiVersion: v1
kind: Pod
metadata:
  name: securitycontext-capabilities-demo
  namespace: default
spec:
  containers:
  - name: demo
    image: ikubernetes/demoapp:v1.0
    imagePullPolicy: IfNotPresent
    command: ["/bin/sh","-c"]
    args: ["/sbin/iptables -t nat -A PREROUTING -p tcp --dport 8080 -j REDIRECT --to-port 80 && /usr/bin/python3 /usr/local/bin/demo.py"]
    securityContext:                                                                                                                                                  
      capabilities:
        add: ['NET_ADMIN']
        drop: ['CHOWN']

验证结果

image-20211203135507324

默认情况下,容器的管理员root 是有权限 改容器内 任何文件的所属主组的。设置了drop chown就不行了

如下

[root@node01 ~]# kubectl exec securitycontext-capabilities-demo   -- chown 1001  /etc/hosts
chown: /etc/hosts: Operation not permitted
command terminated with exit code 1

特权的pod, 以flannel 为例

原因在于kube-proxy的作用是 :

将apiserver 上定义的所有service 变动 ,映射为节点上的ipvs 或iptables规则

​ kube-proxy用到的功能操作都是内核级别的,且部分还超过了CAP_SYS_ADMIN。privileged 设置为true 后,就像是和宿主机上用户是一样的,如果是容器内以管理员的身份运行某个进程,像是在宿主机上以管理员用户执行一样,能直接操作宿主机内核

[root@node01 ~]# kubectl get  po/kube-proxy-65zvn  -n kube-system -o yaml 
   
   - /usr/local/bin/kube-proxy
    - --config=/var/lib/kube-proxy/config.conf
    - --hostname-override=$(NODE_NAME)
    env:
    - name: NODE_NAME
      valueFrom:
        fieldRef:
          apiVersion: v1
          fieldPath: spec.nodeName
    image: registry.aliyuncs.com/google_containers/kube-proxy:v1.19.4
    imagePullPolicy: IfNotPresent
    name: kube-proxy
    resources: {}
    securityContext:
      privileged: true

sysctls:

​ 是让pod内的网络名称空间,可以直接独立的设定名称空间 级别 非 内核系统级别的参数,

​ 事实上,目前在容器内,能允许pod 安全修改的参数只有3个:kernel.shm_rmid_forced, net.ipv4.ip_local_port_range, net.ipv4.tcp_syncookies。而且这3个参数的作用还不是特别的大 . 绝大多数参数是非安全参数,不允许在pod安全上下文内设定。了解就行👌

​ 如果想让pod 内生效那些非安全参数 , 需要在k8s启动时设定 k8s支持非安全参数也允许。在pod内核中设定

方法如下:

​ 在我们启动kubelet的时候,kubelet 有一个配置文件在/etc/default 目录下, 就叫kubelet, 如果不存在,自己建,kubelet 会到这里加载的

[root@node01 default]# cat kubelet 
KUBELET_EXTRA_ARFS='--allowed-unsafe-sysctls=net.core.somaxconn,net.ipv4.ip_unprivileged_port_start'

net.core.somaxconn: 用于设定等待队列长度,系统级入站最大队列长度,默认是128

net.ipv4.ip_unprivileged_port_start 非特权用户可以使用端口的起始值,默认1024,必须重启kubelet,且必须每个节点都改,因为你不知道pod 以后会调度到哪个节点上去

例子: 让容器内普通用户监听80端口

[root@master01 chapter4]# cat securitycontext-sysctls-demo.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: securitycontext-sysctls-demo
  namespace: default
spec:
  securityContext:
    sysctls:
    - name: kernel.shm_rmid_forced
      value: "0"
    - name: net.ipv4.ip_unprivileged_port_start
      value: "0"
  containers:
  - name: demo
    image: ikubernetes/demoapp:v1.0
    imagePullPolicy: IfNotPresent
    securityContext:
      runAsUser: 1001
      runAsGroup: 1001


[root@master01 chapter4]# kubectl  get po/securitycontext-sysctls-demo 
NAME                           READY   STATUS            RESTARTS   AGE
securitycontext-sysctls-demo   0/1     SysctlForbidden   0          22s
[root@master01 chapter4]# 

草?为啥改了kubelet 重启还不行😂

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐