Kubernetes
This section is where the user documentation for your project lives - all the information your users need to understand and successfully use your project.
Kubernetes
This section is where the user documentation for your project lives - all the information your users need to understand and successfully use your project.
kubernetes 名词解释
先介绍一些名词和定义,方便理解演进的过程与出现的项目。
云原生计算基金会,The Cloud Native Computing Foundation ,简称 CNCF。 作为一个厂商中立的基金会,是linxu基金会的一部分。致力于云原生应用的推广。如 kubernetes、Argo、Prometheus、Cilium等。
CNCF全景图,源地址:landscape
每个项目都需要有成熟度等级,有三个级别:
毕业和孵化的项目被认为是稳定的,在生产环境中成功使用的。项目等级是技术委员会采用投票的方式。
云原生技术有利于各组织在公有云、私有云和混合云等新型动态环境中,构建和运行可弹性扩展的应用。云原生的代表技术包括容器、服务网格、微服务、不可变基础设施和声明式API。
这些技术能够构建容错性好、易于管理和便于观察的松耦合系统。结合可靠的自动化手段,云原生技术使工程师能够轻松地对系统做出频繁和可预测的重大变更。
一种流行的现代基础设施自动化的开源工具,像一个数据中心的操作系统,管理在 分布式系统 上运行的应用程序。
Kubernetes 在集群的节点上调度容器。它捆绑几个基础设施结构,如应用程序的实例、负载均衡器、持久性存储等,以一种可以被组成应用程序的方式。
devops 是什么?
DevOps 是指对企业文化、业务自动化和平台设计等方面进行全方位变革,从而实现迅捷、优质的服务交付,提升响应能力和价值。 只有通过快速迭代的服务交付,这一切才能变为现实。
DevOps 可以将传统应用和最新的云原生应用于基础架构彼此相连。
字面意思是 Devlopment(开发)和 Operations(运维)组合而成,但是它代表的理念和实践比单独或者组合的两个词广阔的多。 DevOps 涵盖了安全、协作方式、数据分析等许多方面。
sre 是什么?
站点可靠性工程(SRE)是一种用于 IT 运维的软件工程方案。SRE团队使用软件作为工具,来管理系统、解决问题并实现运维任务自动化。
SRE 执行的任务以前通常是由运维团队手动执行,或者交给使用软件和自动化来解决问题和管理生产系统的工程师或者团队执行。
SRE 工程师是一个独特的岗位,要么必须具备系统管理员背景、或者有运维经验的软件开发人员,要么是有软件开发经验的 IT 运维人员。
SRE 团队根据服务水平协议(SLA)确定新功能的推出,并利用服务水平指标(SLI)和服务水平目标(SLO)定义系统需要的可靠性。
提供服务水平的特定方面。关键 SLI 包括请求延迟性、可用性、错误率和系统吞吐量。
根据 SLI 而指定的服务水平的目标值或者范围。
SRE 要在应用的整个生命周期中确保日常运维任务的自动化和标准化。 Ansible自动化平台 是一个全面的集成平台,可以帮助 SRE 团队实现速度、协作和增长的自动化,从而为企业的技术、 运维和财务职能提供安全性和支持。
容器开放接口
Kubernetes 作为云原生应用的基础调度平台,相当于云原生的操作系统,为了便于系统的扩展,kubernetes 中开放了以下接口,可以分别对接不同的后端,实现自己的业务逻辑:
以上三种资源相当于一个分布式操作系统的最基础的几种资源类型,而 kubernetes 是将它们粘在一起的纽带。
CRI
CRI 是一个插件接口,使 kubelet 能够使用各种容器运行时,无需重新编译集群组件。
CRI 是 kubelet 和容器运行时之间通信的主要协议。CRI 定义了 grpc 协议,用于集群组件 kubelet 与 容器运行时之间的通信。 当通过 grpc 连接到容器运行时,kubelet 充当客户端。运行时和镜像服务端点必须在容器运行时中可用。
k8s v1.24之前的 kubernetes 版本集成了 Docker Engine 的一个组件,名为 dockershim 。在v1.24之后会被移除,需要替换其他的运行时。
几种 kubernetes 常见的容器运行时:
在 Linux 上, CGroup 用于限制分配给进程的资源。
kubelet 和运行时都需要对接控制组来强制执行为 Pod 和容器管理资源,如CPU和Memory。如果要对接控制组,kubelet和容器运行时需要使用一个cgroup驱动。并且 kubelet 和 容器运行时需要使用相同的 cgroup 驱动和相同的配置。
cgroupfs 驱动是 kubelet 中默认的cgroup驱动。当使用 cgroupfs 驱动时,kubelet 和容器运行时将直接对接 cgroup 文件系统来配置 cgroup。
当 systemd 是初始化系统时, 不推荐使用 cgroupfs 驱动,因为 systemd 期望系统上只有一个 cgroup 管理器。因此,如果使用 cgroup v2,则应该使用 systemd cgroup 驱动取代 cgroupfs。
当某个 Linux 系统发行版使用 systemd 作为初始化系统时,初始化进程会生成并使用一个 root 控制组(cgroup),并充当 cgroup 管理器。
当机器上有两个不同的 cgroup 管理器时,会对资源出现两个视图。如将 kublet 和 容器运行时配置为 cgroupfs,但为剩余的进程使用 systemd 的那些节点会在资源压力增大时不稳定。
配置文件: /etc/containerd/config.toml
在 Linux 上,containerd 的默认 CRI 套接字是 /run/containerd/containerd.sock。
Container Storage Interface (CSI)。在 kubernetes V1.13 版本中升级为 GA 。
CSI 为容器编排系统(如 kubernetes)定义标准接口,以将任意存储系统暴露给它们的容器工作负载。
CSI 卷类型是一种 out-tree 的 CSI 卷插件,用于 Pod 与同一节点上运行的外部 CSI 卷驱动程序交互。 部署 CSI 兼容卷驱动后,用户可以使用 csi 作为卷类型来挂载驱动提供的存储。
CSI 卷可以在 Pod中以三种方式使用:
CSI 持久卷具有以下字段可供用户指定:
Out-of-Tree 卷插件包括容器存储接口CSI。它们使存储供应商创建自定义存储插件,而无需将插件源码添加到 kubernetes 代码仓库。
CSI 允许独立于 Kubernetes 代码库开发卷插件,并作为扩展部署(安装)在 Kubernetes 集群上。
以前所有的卷插件都是树内的。“树内”插件是与 Kubernetes 的核心组件一同构建、链接、编译和交付的。这意味着向 Kubernetes 添加新的存储系统(卷插件)需要将 代码合并到 kubernetes 核心代码库中。
通过为 CSI 创建插件 StorageClass 来支持动态配置的 CSI Storage 插件启用自动创建/删除。
例如,以下允许通过名为 com.example.team/csi-driver 的 CSI Volume Plugin 动态创建 fast-storage 的 Volume。
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: fast-storage
provisioner: com.example.team/csi-driver
parameters:
type: pd-ssd
要触发动态配置,需要创建一个 PersistentVolumeClaim 对象。例如,下面的 pvc 可以使用上面的 storageClass 触发动态配置。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-request-for-storage
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: fast-storage
当动态创建 Volume 时,通过 CreateVolume 调用,将参数 type: pd-ssd 传递给 CSI 插件 com.example.team/csi-driver。 作为响应,外部 volume 插件会创建一个新的 Volume,然后自动创建一个 pv 对象来对应前面的 PVC。 然后,kubernetes 会将新的 PersistentVolume 对象绑定到 pvc,使其可以使用。
Container Network Interface(CNI)是k8s网络插件的基础。
Container Network Interface(CNI)是k8s网络插件的基础。基本思想是:Container Runtime 在创建容器时,先创建好 Network Namespace,然后再调用 CNI 插件为这个 net ns 配置网络, 其后再启动容器内的进程。
CNI插件包括两部分:
K8s Pod 中的其他容器都是 Pod 所属 pause 容器的网络,创建过程为:
DHCP 插件是最主要的 IPAM 插件之一,用来通过DHCP方式给容器分配 IP 地址。
host-local 是常见的 CNI IPAM 插件,用来给 container 分配 IP 地址。
Flannel 通过给每台宿主机分配一个子网的方式为容器提供虚拟网络,使用 UDP 封装IP 包来创建 overlay 网络,借助 etcd 维护网络的分配情况。
Calico 是一个基于 BGP 的纯三层的数据中心网络方案,不需要 Overlay。
Calico 在每个计算节点利用 Linux Kernel 实现了一个高效的 VRouter 负责数据转发,而每个 vRoute 通过 BGP 协议负责把自己上运行的 workload 的路由信息像整个 Calico 网络内传播,大规模下可以通过指定的 BGP route reflector 来完成。保证最终所有的 workload 之间的数据流量都是通过 IP 路由的方式完成互联的。
Calico 节点组网可以直接利用数据中心的网络结构,不需要额外的 NAT,隧道或者 Overlay Network。
此外,Calico 基于 iptables 提供了丰富的网络Policy。
Cilium 是一个基于 ebpf 和 xdp 的高性能容器网络方案,提供了 CNI 和 CNM 插件。
Cilium
支持封装路由模式和原生路由模式。
默认的路由模式,在部署时将 tunnel 设置为 vxlan 或者 geneve,它们是两种基于 UDP 的网络封装模式。 在这种模式下,所有集群节点使用基于这样的 UDP 封装协议形成的隧道网络。
将 tunnel 设置为 disabled,cilium会将所有不是发往本地另一个端点的数据包交给Linux内核的路由子系统。 如果所有的节点在同一个 L2 网络上,需要添加 auto-direct-node-routes=true,不需要额外的组件辅助。 如果节点不再同一个 L2 网络上,需要 BGP daemon 组件的辅助。
OPA 是一种轻量级通用策略引擎,可以与服务共处一地。可以将OPA作为 sidecar、主机级守护程序或者库进行集成。
OPA 将策略指定和策略执行分开。当需要做出决策时,会查询 OPA 提供的结构化数据作为输入,OPA 接受任意结构化数据作为输入。
策略通常有:
表达式:
变量:
s := input.servers[0]
s.id == "app"
p := s.protocols[0]
p == "https"
当 OPA 计算表达式时,会找到使所有表达式都为真的变量的值。如果没有给所有为真的变量赋值,则结果未定义。
变量是不可变的,如果尝试两次分配同一个变量。OPA会报告错误。
opa必须要能够枚举表达式总和功能所有变量的值。如果OPA无法枚举任何表达式中变量的值,则opa报错。
迭代:
some i : input.networks[i].public == true
# 返回的 i 即为数组的下标
some i,j : input.services[i].protocols[j] == "http"
# 返回 i 和 j 的下标
some i,j
id := input.ports[i].id
public_network contians net.d if{
some net in input.networks
net.public
}
shell_accessible containers server.id if{
some server in input.servers
"telnet" in server.protocols
}
shell_accessible contians server.id if{
some server in input.servers
"ssh" in server.protocols
}
对于所有 every
no_telnet_exposed if {
every server in input.servers {
every protocol in server.portocols {
"telnet" != protocol
}
}
}
Rego 允许使用规则封装和重用逻辑。规则只是如果/那么逻辑语句,规则可以是完整的或者部分的。
any_public_networks := true if { # head
sone net in input.networks # body
net.public
}
public_network contains net.id if{
some net in input.networks
net.public
}
input 的 servers 中只要有一个就是正确的
package example.logical_or
default shell_accessible := false
shell_accessible := true {
input.servers[_].protocols[_] == "telnet"
}
shell_accessible := true {
input.servers[_].protocols[_] == "ssh"
}
使用 Rego 定义易于读写的策略。 Rego 专注于为引用嵌套文档提供强大的支持,并确保查询正确无误。 Rego 是声明性的,因此策略作者可以专注于应该返回什么查询,而不是应该如何执行查询。这些查询比命令式语言中的等效查询更简单、更简洁。 与支持声明式查询语言的其他应用程序一样,OPA能够优化查询以提高性能。
基本语法:
输入会挂在 input 对象下,用到的上下文会挂在data对象下。
rule: 当定义规则时:
具体到代码中规则 allow ,默认值是 false 要求 user_has_role 和 role_has_permission 同时满足,两者的 role_name 也是一样。
你可能发现,局部变量 role_name 没声明,Rego里可以省略声明局部变量,直接使用。
其中 user_has_role[role_name] 这种带参数的结构不是规则,叫虚拟文档。
OPA gatekeeper 是一个专业项目,提供 OPA 和 kubernetes 之间的一些集成。
OPA GateKeeper 在普通 OPA 之上添加了以下内容:
Kubernetes API 服务器被分配为在创建、更新、删除对象时查询 OPA 以准入控制决策。
ApiServer 将 webhook 请求中的整个kubernetes对象发送给 OPA ,OPA 使用准入审查来评估加载的策略 input。
Input 文档包含以下字段:
利用 kubeadm 和 二进制 两种方式搭建集群。
可以搭建单master集群,管理多个node节点。也可以使用多个master节点,管理多个node节点,同时对多个apiserver使用负载均衡。
一般测试环境搭建:
生产环境:
kubeadm工具能够通过两条指令完成一个 kubernetes 集群的部署。
https://kubernetes.io/zh-cn/docs/setup/production-environment/tools/kubeadm/install-kubeadm/
master: 192.168.177.130 node1: 192.168.177.131 node2: 192.168.177.132
在每台机器上执行以下命令:
# 关闭防火墙
systemctl stop firewalld
systemctl disable firewalld
# 关闭 selinux
# 永久关闭
sed -i 's/enforcing/disabled/' /etc/selinux/config
# 临时关闭
setenforce 0
# 关闭 swap
# 永久关闭
sed -ri 's/.*swap.*/#&/' /etc/fstab
# 临时关闭
swapoff -a
# 规划主机名(各个机器不同执行)
hostnamectl set-hostname k8smaster/k8snode1/k8snode2
# 添加hosts
cat >> /etc/hosts << EOF
192.168.177.130 k8smaster
192.168.177.131 k8snode1
192.168.177.132 k8snode2
EOF
# 将桥接的 Ipv4 流量传递到 iptables 的链
cat > /etc/sysctl.d/k8s.conf << EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
# 生效
sysctl --system
# 时间同步
yum install ntpdata -y
ntpodata time.windows.com
配置docker源
https://developer.aliyun.com/mirror/?spm=a2c6h.13651104.0.d1002.2cd533174OAYBv
# 使用阿里源
cat >/etc/yum.repos.d/docker.repo<<EOF
[docker-ce-edge]
name=Docker CE Edge - \$basearch
baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/7/\$basearch/edge
enabled=1
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg
EOF
安装docker
# yum安装
yum -y install docker-ce
# 查看docker版本
docker --version
# 启动docker
systemctl enable docker
systemctl start docker
# 配置docker的镜像源
cat >> /etc/docker/daemon.json << EOF
{
"registry-mirrors": ["https://b9pmyelo.mirror.aliyuncs.com"]
}
EOF
#重启docker
systemctl restart docker
配置k8s源
cat > /etc/yum.repos.d/kubernetes.repo << EOF
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
安装 kubeadm kubelet kubectl
# 安装kubelet、kubeadm、kubectl,同时指定版本
yum install -y kubelet-1.18.0 kubeadm-1.18.0 kubectl-1.18.0
# 设置开机启动
systemctl enable kubelet
kubeadm init --apiserver-advertise-address=192.168.177.130 --image-repository registry.aliyuncs.com/google_containers --kubernetes-version v1.18.0 --service-cidr=10.96.0.0/12 --pod-network-cidr=10.244.0.0/16
当出现 successfully 字样时表示已经安装成功。最下面有工作节点添加进集群的命令,需要copy。
#使用 kubectl 工具
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
# 检查,会发现有一个master节点,但是状态是 NotReady
kubectl get nodes
复制 kubeadm init … 输出的 kubeadm join 命令执行。默认 token 的有效期是 24h,当过期之后需要重新创建token。
kubeadm token create --print-join-command
当我们把两个节点都加入进来后,可以去 master 节点执行下面命令:
# 出现三个节点,但是状态都是 NotReady
kubectl get node
# 下载网络插件配置,需要修改镜像
wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
# apply 一下创建,之后会发现已经 Ready
# 如果还是有些节点处于 NotReady 状态,可以在master节点删除该节点,然后节点重置之后重新加入。
kubectl delete node k8snode1
#在 k8snode1 节点上进行重置
kubeadm reset
# 重置完成后加入
kubeadm join 192.168.177.130:6443 --token 8j6ui9.gyr4i156u30y80xf --discovery-token-ca-cert-hash sha256:eda1380256a62d8733f4bddf926f148e57cf9d1a3a58fb45dd6e80768af5a500
两台机器,vms71和vms72
系统centos7.4
识别地址
cat /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.26.71 vms71.rhce.cc vms71
192.168.26.72 vms72.rhce.cc vms72
关闭swap分区
swapoff -a ; sed -i '/fstab/d' /etc/fstab
更新 yum 源
rm -rf /etc/yum.repos.d/* ; wget ftp://ftp.rhce.cc/k8s/* -P /etc/yum.repos.d/ #一般更新为阿里源
yum clean all
安装 containerd
yum install containerd.io cri-tools -y
crictl config runtime-endpoint unix:///var/run/containerd/containerd.sock
先生成配置文件/etc/containerd/config.toml
containerd config default > /etc/containerd/config.toml
编辑配置文件,改成:
[plugins."io.containerd.grpc.v1.cri".registry.mirrors]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
endpoint = ["https://frz7i079.mirror.aliyuncs.com"]
# 修改 sandbox_image
[plugins."io.containerd.grpc.v1.cri"]
# enable SELinux labeling
enable_selinux = true
sandbox_image = "XX/pause:3.6"
# 根据cgroup版本配置是否为true
SystemdCgroup = false
在所有节点重启 containerd ,并设置开机自动启动
systemctl enable containerd
systemctl restart containerd
在所有机器上执行命令,目的是系统重启时能自动加载。
cat > /etc/modules-load.d/containerd.conf <<EOF
overlay
br_netfilter
EOF
执行以下内核命令
cat <<EOF > /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
EOF
sysctl -p /etc/sysctl.d/k8s.conf
安装 nerdctl(containerd cli,可选) https://github.com/containerd/nerdctl/releases https://github.com/containernetworking/plugins/releases
修改环境配置文件
vim /etc/profile
#添加
source <(nerdctl completion bash)
export CONTAINERD_NAMESPACE=k8s.io
# 生效
source /etc/profile
安装 cfssl 工具,放在 /usr/local/bin 中。
# ls
cfssl-certinfo_linux-amd64 cfssljson_linux-amd64 cfssl_linux-amd64
#for i in *;do n=${i%_*};mv $i $n;done;chmod +x *
# ls
cfssl cfssl-certinfo cfssljson
# 移动到 /usr/local/bin 文件夹
操作在一个机器上操作,然后将生成的正式拷贝到 /etc/kubernetes/pki 里。
mkdir -p /etc/kubernetes/pki
# cfssl print-defaults config > ca-config.json
# cat ca-config.json
{
"signing": {
"default": {
"expiry": "87600h"
},
"profiles": {
"www": {
"expiry": "87600h",
"usages": [
"signing",
"key encipherment",
"server auth",
"client auth"
]
}
}
}
}
# cfssl print-defaults csr > ca-csr.json
# cat ca-csr.json
{
"CN": "kubernetes",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "CN",
"ST": "Shanxi",
"L": "xian",
"O": "kubernetes",
"OU": "system"
}
]
}
# cfssl gencert -initca ca-csr.json |cfssljson -bare ca
# ls
ca-config.json ca.csr ca-csr.json ca-key.pem ca.pem
创建聚合层(Aggregation Layer)所需要的证书,如果环境里没有配置聚合层,这步和下步创建证书的步骤是不需要的。
生成 front-proxy-ca 自签名证书
cp ca-config.json front-proxy-ca-config.json
cp ca-csr.json front-proxy-ca-csr.json
cfssl gencert -initca front-proxy-ca-csr.json | cfssljson -bare front-proxy-ca
聚合层客户端证书
cfssl print-defaults csr > front-proxy-client-csr.json
{
"CN": "front-proxy-client",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "CN",
"ST": "Shanxi",
"L": "XIan",
"O": "kubernetes",
"OU": "system"
}
]
}
cfssl gencert -ca=front-proxy-ca.pem -ca-key=front-proxy-ca-key.pem -config=front-proxy-ca-config.json -profile=www front-proxy-client-csr.json | cfssljson -bare front-proxy-client
etcd需要跟apiserver进行双向tls认证,etcd服务器端需要证书和私钥,先创建etcd证书请求文件。
先创建etcd服务端证书
cfssl print-defaults csr > etcd-csr.json
{
"CN": "etcd",
"key": {
"algo": "rsa",
"size": 2048
},
"hosts": [
"127.0.0.1",
"192.168.26.71",
"xxx"
],
"names": [
{
"C": "CN",
"ST": "Shanxi",
"L": "Xian",
"O": "kubernetes",
"OU": "system"
}
]
}
# 颁发 etcd 服务器端所需要的证书
cfssl gencert -ca-ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=www etcd-csr.json | cfssljson -bare etcd
ls etcd*.pem
etcd-key.pem etcd.pem
再创建 etcd客户端证书,当 apiserver向 etcd 连接时向etcd出示的证书
cfssl print-defaults csr > etcd-client-csr.json
cat etcd-client-csr.json
{
"CN": "etcd",
"key": {
"algo": "rsa",
"size": 2048
},
"hosts": [
"127.0.0.1",
"192.168.26.71",
"xxx"
],
"names": [
{
"C": "CN",
"ST": "Shanxi",
"L": "Xian",
"O": "kubernetes",
"OU": "system"
}
]
}
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=www etcd-client-csr.json | cfssljson -bare etcd-client
ls etcd-client*.pem
etcd-clinet-key.pem etcd-client.pem
k8s其他组件要跟 apiserver进行双向tls认证,所以apiserver需要自己的证书,下面生成apiserver所需要的证书请求文件。
cfssl print-defaults csr > apiserver-csr.json
{
"CN": "kubernetes",
"key": {
"algo": "rsa",
"size": 2048
},
"hosts": [
"127.0.0.1",
"192.168.26.71", # master节点的Ip填上
"192.168.26.72",
"10.96.0.1", # service网络的第一个ip
"kubernetes",
"kubernetes.default",
"kubernetes.default.svc",
"kubernetes.default.svc.cluster",
"kubernetes.default.svc.cluster.local"
],
"names": [
{
"C": "CN",
"ST": "Shanxi",
"L": "Xian",
"O": "kubernetes",
"OU": "system"
}
]
}
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=www apiserver-csr.json | cfssljson -bare apiserver
ls apiserver*.pem
apiserver-key.pem apiserver.pem
controller-manager 需要跟apiserver 进行 mtls 认证,生成证书申请请求文件
cfssl print-defaults csr > controller-manager-csr.json
{
"CN": "system:kube-controller-manager",
"key": {
"algo": "rsa",
"size": 2048
},
"hosts": [
"127.0.0.1",
"192.168.26.71" # 所有 节点ip
],
"names": [
{
"C": "CN",
"ST": "Shanxi",
"L": "Xian",
"O": "system:kube-controller-manager",
"OU": "system"
}
]
}
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=www controller-manager-csr.json | cfssljson -bare controller-manager
ls -1 control*.pem
controller-manager-key.pem controller-manager.pem
schedule 也用相同的方式,kube-proxy 也用相同的方式(没有host)
cfssl print-defaults csr > admin-csr.json
cat admin-csr.json
{
"CN": "admin",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "CN",
"ST": "Jiangsu",
"L": "Xuzhou",
"O": "system:masters",
"OU": "system"
}
]
}
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=www admin-csr.json | cfssljson -bare admin
把所有证书都拷贝到 /etc/kubernetes/pki 里。
$ ls /etc/kubernetes/pki
admin-key.pem ca-key.pem etcd-client-key.pem front-proxy-ca-key.pem proxy-key.pem admin.pem ca.pem etcd-client.pem front-proxy-ca.pem proxy.pem
apiserver-key.pem controller-manager-key.pem etcd-key.pem front-proxy-client-key.pem scheduler-key.pem
apiserver.pem controller-manager.pem etcd.pem front-proxy-client.pem scheduler.pem
下载地址 https://github.com/etcd-io/etcd/releases/download/v3.5.4/etcd-v3.5.4-linux-amd64.tar.gz 将下载后的可执行文件拷贝到 /usr/local/bin 目录下。
创建两个目录
mkdir /etc/etcd /var/lib/etcd
配置文件
cat /etc/etcd/etcd.conf
#[Member]
ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_PEER_URLS="https://192.168.26.71:2380"
ETCD_LISTEN_CLIENT_URLS="https://192.168.26.71:2379,http://127.0.0.1:2379"
ETCD_NAME="etcd1"
#[Clustering]
ETCD_INITIAL_ADVERTISE_PEER_URLS="https://192.168.26.71:2380"
ETCD_ADVERTISE_CLIENT_URLS="https://192.168.26.71:2379"
ETCD_INITIAL_CLUSTER="etcd1=https://192.168.26.71:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_INITIAL_CLUSTER_STATE="new"
创建etcd的启动脚本。 /usr/lib/systemd/system/etcd.service
[Unit]
Description=Etcd Server
After=network.target
After=network-online.target
Wants=network-online.target
[Service]
Type=notify
EnvironmentFile=-/etc/etcd/etcd.conf
WorkingDirectory=/var/lib/etcd/
ExecStart=/usr/local/bin/etcd \
--cert-file=/etc/kubernetes/pki/etcd.pem \
--key-file=/etc/kubernetes/pki/etcd-key.pem \
--trusted-ca-file=/etc/kubernetes/pki/ca.pem \
--peer-cert-file=/etc/kubernetes/pki/etcd.pem \
--peer-key-file=/etc/kubernetes/pki/etcd-key.pem \
--peer-trusted-ca-file=/etc/kubernetes/pki/ca.pem \
--peer-client-cert-auth \
--client-cert-auth
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
启动etcd并设置开机自启
systemctl enable etcd --now
systemctl is-actiive etcd
下载对应的二进制包,解压到对应的目录。如:/usr/bin/
后面配置 kubelet 的 bootstrap 认证,即 kubelet 启动时自动创建 csr 请求,这里需要在 apiserver 上开启 token 的认证,所以现在master上生成一个随机值作为token。
openssl rand -hex 10
xxxxxxxxx
将token导入到文件里,这里写入到 /etc/kubernetes/token.csv,格式为 token,user,uid,groups
xxxxxxxxx,kubelet-bootstrap,10001,"system:node-bootstrapper"
创建 apiserver 启动脚本:
cat /usr/lib/systemd/system/kube-apiserver.service
[Unit]
Description=Kubernetes API Server
Documentation=https://github.com/kubernetes/kubernetes
After=network.target
[Service]
ExecStart=/usr/bin/kube-apiserver \
--v=2 \
--logtostderr=true \
--allow-privileged=true \
--bind-address=192.168.26.71 \
--secure-port=6443 \
--token-auth-file=/etc/kubernetes/token.csv \
--advertise-address=192.168.26.71 \
--service-cluster-ip-range=10.96.0.0/16 \
--service-node-port-range=30000-60000 \
--etcd-servers=https://192.168.26.71:2379 \
--etcd-cafile=/etc/kubernetes/pki/ca.pem \
--etcd-certfile=/etc/kubernetes/pki/etcd.pem \
--etcd-keyfile=/etc/kubernetes/pki/etcd-key.pem \
--client-ca-file=/etc/kubernetes/pki/ca.pem \
--tls-cert-file=/etc/kubernetes/pki/apiserver.pem \
--tls-private-key-file=/etc/kubernetes/pki/apiserver-key.pem \
--kubelet-client-certificate=/etc/kubernetes/pki/apiserver.pem \
--kubelet-client-key=/etc/kubernetes/pki/apiserver-key.pem \
--service-account-key-file=/etc/kubernetes/pki/ca-key.pem \
--service-account-signing-key-file=/etc/kubernetes/pki/ca-key.pem \
--service-account-issuer=https://kubernetes.default.svc.cluster.local \
--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname \
--enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,ResourceQuota \
--authorization-mode=Node,RBAC \
--enable-bootstrap-token-auth=true
#--requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.pem \
#--proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.pem \
#--proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client-key.pem \
#--requestheader-allowed-names=aggregator \
#--requestheader-group-headers=X-Remote-Group \
#--requestheader-extra-headers-prefix=X-Remote-Extra- \
#--requestheader-username-headers=X-Remote-User
Restart=on-failure
RestartSec=10s
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
几个选项作用:
启动 apiserver
systemctl start kube-apiserver
systemctl enable kube-apiserver
配置 controller-manager
cat /usr/lib/systemd/system/kube-controller-manager.service
[Unit]
Description=Kubernetes Controller Manager
Documentation=https://github.com/kubernetes/kubernetes
After=network.target
[Service]
ExecStart=/usr/bin/kube-controller-manager \
--v=2 \
--logtostderr=true \
--bind-address=127.0.0.1 \
--root-ca-file=/etc/kubernetes/pki/ca.pem \
--cluster-signing-cert-file=/etc/kubernetes/pki/ca.pem \
--cluster-signing-key-file=/etc/kubernetes/pki/ca-key.pem \
--service-account-private-key-file=/etc/kubernetes/pki/ca-key.pem \
--kubeconfig=/etc/kubernetes/kube-controller-manager.kubeconfig \
--leader-elect=true \
--use-service-account-credentials=true \
--node-monitor-grace-period=40s \
--node-monitor-period=5s \
--pod-eviction-timeout=2m0s \
--controllers=*,bootstrapsigner,tokencleaner \
--allocate-node-cidrs=true \
--cluster-cidr=10.244.0.0/16 \
--node-cidr-mask-size=24
Restart=always
RestartSec=10s
[Install]
WantedBy=multi-user.target
创建 controller-manager 所需要的 kubeconfig 文件 – kube-controller-manager.kubeconfig
controller-manager 和 apierver 之间的认证是通过 kubeconfig 的方式来认证的,即 controller-manager 的私钥,公钥,ca的证书放在一个 kubeconfig 文件里。 下面创建 controller-manager 所用的 kubeconfig 文件 kube-controller-manager.kubeconfig
cd /etc/kubernetes/pki/
# 设置集群信息
kubectl config set-cluster kubernetes --certificate-authority=ca.pem --embed-certs=true --server=https://192.168.26.71:6443 --kubeconfig=kube-controller-manager.kubeconfig
# 设置用户信息
kubectl config set-credentials system:kube-controller-manager --client-certificate=controller-manager.pem --client-key=controller-manager-key.pem --embed-certs=true --kubeconfig=kube-controller-manager.kubeconfig
# 设置上下文信息
kubectl config set-context system:kube-controller-manager --cluster=kubernetes --user=system:kube-controller-manager --kubeconfig=kube-controller-manager.kubeconfig
# 设置默认的上下文
kubectl config use-context system:kube-controller-manager --kubeconfig=kube-controller-manager.kubeconfig
#放到合适的地方
mv kube-controller-manager.kubeconfig /etc/kubernetes/
启动和开机自启
systemctl deamon-reload
systemctl enable kube-controller-manager --now
systemctl start kube-controller-manager
配置和启动 scheduler
cat /usr/lib/systemd/system/kube-scheduler.service
[Unit]
Description=Kubernetes Scheduler
Documentation=https://github.com/kubernetes/kubernetes
After=network.target
[Service]
ExecStart=/usr/bin/kube-scheduler \
--v=2 \
--logtostderr=true \
--bind-address=127.0.0.1 \
--leader-elect=true \
--kubeconfig=/etc/kubernetes/kube-scheduler.kubeconfig
Restart=always
RestartSec=10s
[Install]
WantedBy=multi-user.target
创建scheduler 的 kubeconfig 和启动参考上文。
最后创建admin能用的kubeconfig
kubelet 为用户 kubelet-bootstrap 授权,允许 kubelet tls bootstrap 创建 CSR 请求。
kubectl create clusterrolebinding kubelet-bootstrap1 --clusterrole=system:node-bootstrapper --user=kubelet-bootstrap
把 system:certificates.k8s.io:certificatesigningrequests:nodeclient 授权给 kubelet-bootstrap ,目的是为了实现对 csr 的自动审批。
kubectl create clusterrolebinding kubelet-bootstrap2 --clusterrole=system:certificates.k8s.io:certificatesigningrequests:nodeclient --user=kubelet-bootstrap
创建 kubelet bootstap 的 kubeconfig 文件。
创建启动脚本 ,后面会把 kubelet 颁发的证书放在 /var/lib/kubelet/pki 里,所以先把此目录创建出来。
mkdir -p /var/lib/kubelet/pki /var/log/kubernetes
cat /usr/lib/systemd/system/kubelet.service
[Unit]
Description=Kubernetes Kubelet
Documentation=https://github.com/kubernetes/kubernetes
After=containerd.service
Requires=containerd.service
[Service]
WorkingDirectory=/var/lib/kubelet
ExecStart=/usr/bin/kubelet \
--bootstrap-kubeconfig=/etc/kubernetes/kubelet-bootstrap.conf \
--cert-dir=/var/lib/kubelet/pki \
--kubeconfig=/etc/kubernetes/kubelet.kubeconfig \
--config=/etc/kubernetes/kubelet-config.yaml \
--pod-infra-container-image=registry.aliyuncs.com/google_containers/pause:3.7 \
--container-runtime=remote \
--container-runtime-endpoint=unix:///var/run/containerd/containerd.sock \
--runtime-request-timeout=15m \
--alsologtostderr=true \
--logtostderr=false \
--log-dir=/var/log/kubernetes \
--v=2
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
创建 kubelet 用的配置文件 kubelet-config.yaml
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
address: 0.0.0.0
port: 10250
readOnlyPort: 10255
authentication:
anonymous:
enabled: false
webhook:
cacheTTL: 0s
enabled: true
x509:
clientCAFile: /etc/kubernetes/pki/ca.pem
authorization:
mode: Webhook
webhook:
cacheAuthorizedTTL: 0s
cacheUnauthorizedTTL: 0s
cgroupDriver: systemd
clusterDNS:
- 10.96.0.10
clusterDomain: cluster.local
cpuManagerReconcilePeriod: 0s
evictionPressureTransitionPeriod: 0s
fileCheckFrequency: 0s
healthzBindAddress: 127.0.0.1
httpCheckFrequency: 0s
imageMinimumGCAge: 0s
这里指定的 cluster dns 的ip是 10.96.0.10
最后将kubelet 能用到的文件同步到所有的worker节点上,启动kubelet并设置开机自启。
查看 csr已经有申请请求自动审批了。
安装 kube-proxy
kube-proxy.yaml
apiVersion: kubeproxy.config.k8s.io/v1alpha1
bindAddress: 0.0.0.0
clientConnection:
kubeconfig: /etc/kubernetes/kube-proxy.kubeconfig
clusterCIDR: 10.244.0.0/16
kind: KubeProxyConfiguration
metricsBindAddress: 0.0.0.0:10249
mode: "ipvs"
再创建一个 kubeconfig 同上
[Unit]
Description=Kubernetes Kube-Proxy Server
Documentation=https://github.com/kubernetes/kubernetes
After=network.target
[Service]
WorkingDirectory=/var/lib/kube-proxy
ExecStart=/usr/bin/kube-proxy \
--config=/etc/kubernetes/kube-proxy.yaml \
--alsologtostderr=true \
--logtostderr=false \
--log-dir=/var/log/kubernetes \
--v=2
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
rbac
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:kubernetes-to-kubelet
rules:
- apiGroups:
- ""
resources:
- nodes/proxy
- nodes/stats
- nodes/log
- nodes/spec
- nodes/metrics
verbs:
- "*"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: system:kubernetes
namespace: ""
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:kubernetes-to-kubelet
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: kubernetes
安装网络插件,部署coredns,最后测试.
使用 velero 进行k8s集群和持久卷的备份、迁移和灾难恢复。
可以使用 velero cli 或者 helm 进行安装。
启动 velero ,创建定期备份,一般每天凌晨备份一次,可以忽略持久化备份。
Kubernetes 审计(Audit)提供了安全相关的时序操作记录,支持 日志 和 webhook 两种格式,并可以通过审计策略自定义事件类型。
kubernetes 审计功能提供了与安全相关的、按照事件顺序排列的记录集,记录每个用户、使用 kubernetes API 的应用以及控制面自身引发的活动。
审计记录最初产生于 kube-apiserver 内部,每个请求在不同阶段都会生成审计事件,这些事件会根据特定策略被预处理并写入后端。 策略确定要记录的内容和用来存储记录的后端。
每个请求都可被记录其相关的阶段(stage),已定义的阶段有:
审计策略定义了相关应记录哪些事件以及哪些数据的规则。审计策略对象结构定义在 audit.k8s.io API组。处理事件时,按照顺序与规则列表进行比较。
第一个匹配规则设置事件的 审计级别(Audit Level)。已定义的审计级别有:
使用 –audit-policy-file 参数指定策略,传递给 kube-apiserver 。如果不设置该标志,则不记录事件。 注意 rules 字段必须在审计策略文件中提供,没有规则的策略将被视为非法配置。
配置 kube-apiserver 的参数开启审计日志。
eg:
--audit-log-format=json
--audit-log-maxage=30
--audit-log-maxbackup=10
--audit-log-maxsize=100
--audit-log-path=/var/log/audit/kube-audit.log
--audit-policy-file=/etc/kubernetes/policies/audit-policy.yaml
eg:
{"kind":"Event","apiVersion":"audit.k8s.io/v1","level":"Metadata","auditID":"5737a616-9f5a-4efe-98f8-90f5b1b56880","stage":"ResponseComplete","requestURI":"/api/v1/namespaces/default/configmaps/console-operator-lock","verb":"get","user":{"username":"system:serviceaccount:default:default-sa","uid":"1640a9e4-eef5-4b39-8d6d-871fef41107b","groups":["system:serviceaccounts","system:serviceaccounts:default","system:authenticated"],"extra":{"authentication.kubernetes.io/pod-name":["console-operator-7bd67974fd-bvxf4"],"authentication.kubernetes.io/pod-uid":["275b6b2a-f58f-4443-8544-e277c24138a0"]}},"sourceIPs":["10.222.8.92"],"userAgent":"console-operator/v0.0.0 (linux/amd64) kubernetes/$Format/leader-election","objectRef":{"resource":"configmaps","namespace":"default","name":"console-operator-lock","apiVersion":"v1"},"responseStatus":{"metadata":{},"code":200},"requestReceivedTimestamp":"2023-04-06T01:57:25.529046Z","stageTimestamp":"2023-04-06T01:57:25.538347Z","annotations":{"authorization.k8s.io/decision":"allow","authorization.k8s.io/reason":"RBAC: allowed by ClusterRoleBinding \"default-sa\" of ClusterRole \"default-sa\" to ServiceAccount \"default-sa/default\""}}
默认配置在 $HOME/.kube/config ,或者指定配置文件 kubectl –kubeconfig 。
可以使用环境变量 KUBECONFIG 。
--v=0 Generally useful for this to ALWAYS be visible to an operator.
--v=1 A reasonable default log level if you don’t want verbosity.
--v=2 Useful steady state information about the service and important log messages that may correlate to significant changes in the system. This is the recommended default log level for most systems.
--v=3 Extended information about changes.
--v=4 Debug level verbosity.
--v=6 Display requested resources.
--v=7 Display HTTP request headers.
--v=8 Display HTTP request contents
# 1. 获取token
cat << EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: custome-sa
namespace: default
---
apiVersion: v1
kind: Secret
metadata:
name: custome-sa
namespace: default
annotations:
kubernetes.io/service-account.name: "custome-sa"
namespace: default
type: kubernetes.io/service-account-token
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: sa-crb
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: custome-sa
namespace: default
EOF
TOKEN=`kubectl get secret custome-sa --template={{.data.token}} | base64 --decode`
echo $TOKEN
#2. 直接访问
curl --insecure -H "Authorization: Bearer $TOKEN" https://xxx:443/api/v1/namespaces/default/services
# 或者可以使用本地代理访问
kubectl proxy --port 8080
curl --insecure -H "Authorization: Bearer $TOKEN" https://127.0.0.1:8080/api/v1/namespaces/default/services
在 Pod 内通过 serviceaccount 内访问。
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: custome-sa
namespace: default
---
apiVersion: v1
kind: Secret
metadata:
name: custome-sa
annotations:
kubernetes.io/service-account.name: "custome-sa"
namespace: default
type: kubernetes.io/service-account-token
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: pods-list
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["list"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: pods-list
subjects:
- kind: ServiceAccount
name: custome-sa
namespace: default
roleRef:
kind: ClusterRole
name: pods-list
apiGroup: rbac.authorization.k8s.io
EOF
TOKEN=`kubectl get secret custome-sa --template={{.data.token}} | base64 --decode`
echo $TOKEN
# 创建工作负载时指定sa
spec:
serviceAccountName: custome-sa
# 在 pod 内访问
export CURL_CA_BUNDLE=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
curl -H "Authorization: Bearer $TOKEN" https://kubernetes
Client-go 目录结构:
一般使用客户端命令进行备份操作。
# 添加远程,需要 access key
mc config host add my-minio https://xxx --api S3v4
# 上传
mc cp {local/path} my-minio/backup/ #可以用 * 上传目录下所有的文件
# 下载所有backup backut下的文件到本地当前目录
mc cp --recursive my-minio/backup .
# 查看所有的bucket
mc ls my-minio
NAME:
mc - MinIO Client for cloud storage and filesystems.
USAGE:
mc [FLAGS] COMMAND [COMMAND FLAGS | -h] [ARGUMENTS...]
COMMANDS:
alias set, remove and list aliases in configuration file
ls list buckets and objects
mb make a bucket
rb remove a bucket
cp copy objects
mirror synchronize object(s) to a remote site
cat display object contents
head display first 'n' lines of an object
pipe stream STDIN to an object
share generate URL for temporary access to an object
find search for objects
sql run sql queries on objects
stat show object metadata
mv move objects
tree list buckets and objects in a tree format
du summarize disk usage recursively
retention set retention for object(s)
legalhold manage legal hold for object(s)
diff list differences in object name, size, and date between two buckets
rm remove objects
version manage bucket versioning
ilm manage bucket lifecycle
encrypt manage bucket encryption config
event manage object notifications
watch listen for object notification events
undo undo PUT/DELETE operations
policy manage anonymous access to buckets and objects
tag manage tags for bucket and object(s)
replicate configure server side bucket replication
admin manage MinIO servers
update update mc to latest release
GLOBAL FLAGS:
--autocompletion install auto-completion for your shell
--config-dir value, -C value path to configuration folder (default: "/root/.mc")
--quiet, -q disable progress bar display
--no-color disable color theme
--json enable JSON lines formatted output
--debug enable debug output
--insecure disable SSL certificate verification
--help, -h show help
--version, -v print the version
TIP:
Use 'mc --autocompletion' to enable shell autocompletion
VERSION:
RELEASE.2021-03-23T05-46-11Z
在github上的 cfssl源码和下载地址 。
mkdir ~/bin
curl -s -L -o ~/bin/cfssl https://pkg.cfssl.org/R1.2/cfssl_linux-amd64
curl -s -L -o ~/bin/cfssljson https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64
curl -s -L -o ~/bin/cfssl-certinfo https://pkg.cfssl.org/R1.2/cfssl-certinfo_linux-amd64
chmod +x ~/bin/{cfssl,cfssljson,cfssl-certinfo}
# 临时生效
export PATH=$PATH:~/bin
# 写到文件中 export PATH=$PATH:/root/bin
source /etc/profile
mkdir ~/cfssl
cd ~/cfssl
cfssl print-defaults config > ca-config.json
cfssl print-defaults csr > ca-csr.json
以生成 kubernetes 或者 etcd 自签证书为例:
一般来说,profiles 节点有多个配置,server端证书带有 server auth ,client端证书带有 client auth ,peer 端的证书需要 server auth 和 client auth。时间默认值为 8760h
"profiles": {
"server": {
"expiry": "8760h",
"usages": [
"signing",
"key encipherment",
"server auth"
]
},
"client": {
"expiry": "8760h",
"usages": [
"signing",
"key encipherment",
"client auth"
]
},
"peer": {
"expiry": "8760h",
"usages": [
"signing",
"key encipherment",
"server auth",
"client auth"
]
}
}
这里为了简单只用一个 profile ,服务端,客户端,peer 公用一个证书。 ca-cofnig.json
{
"signing": {
"default": {
"expiry": "87600h"
},
"profiles": {
"etcd": {
"expiry": "87600h",
"usages": [
"signing",
"key encipherment",
"server auth",
"client auth"
]
}
}
}
}
ca-csr.json
{
"CN": "etcd",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "US",
"L": "CA",
"ST": "San Francisco",
"O": "Etcd"
}
]
}
cfssl gencert -initca ca-csr.json |cfssljson -bare ca-etcd -
执行完成后得到以下文件
ca-etcd-key.pem
ca-etcd.csr
ca-etcd.pem
cfssl print-defaults csr > etcd-server.json
服务器证书最重要的值是 CN 和 hosts,必须替换它们。
...
"CN": "etcd1",
"hosts": [
"192.168.122.68",
"etcd.example.com",
"etcd1"
],
...
生成 server 证书和私钥:
cfssl gencert -ca-ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=etcd etcd-server.json | cfssljson -bare cert-etcd-server
将生成以下文件:
cert-etcd-server-key.pem
cert-etcd-server.csr
cert-etcd-server.pem
cfssl print-defaults csr > member1.json
替换 CN 和 hosts 。
...
"CN": "member1",
"hosts": [
"192.168.122.101",
"member1.example.com",
"member1"
],
...
生成 member1 的证书和私钥:
cfssl gencert -ca-ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=etcd member1.json | cfssljson -bare cert-member1
cfssl print-defaults csr > client.json
客户端证书,可以忽略 hosts ,仅将 CN 设置为客户端值。
...
"CN": "client",
...
生成 client 的证书和私钥:
cfssl gencert -ca-ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=etcd client.json | cfssljson -bare client
openssl x509 -in ca.pem -text -noout
openssl x509 -in server.pem -text -noout
openssl x509 -in client.pem -text -noout
最主要的还是阅读官方文档,下面演示一些操作。生成证书可以参考上面 cfssl使用 文章。
下面演示在一个节点起三个etcd节点。
etcd --name etcd1 \
--data-dir /root/etcd/etcd1 \
--initial-advertise-peer-urls http://172.31.239.82:12380 \
--listen-peer-urls http://172.31.239.82:12380 \
--listen-client-urls http://172.31.239.82:12379,http://127.0.0.1:12379 \
--advertise-client-urls http://172.31.239.82:12379 \
--initial-cluster-token token \
--initial-cluster etcd1=http://172.31.239.82:12380,etcd2=http://172.31.239.82:22380,etcd3=http://172.31.239.82:32380 \
--initial-cluster-state new
etcd --name etcd2 \
--data-dir /root/etcd/etcd2 \
--initial-advertise-peer-urls http://172.31.239.82:22380 \
--listen-peer-urls http://172.31.239.82:22380 \
--listen-client-urls http://172.31.239.82:22379,http://127.0.0.1:22379 \
--advertise-client-urls http://172.31.239.82:22379 \
--initial-cluster-token token \
--initial-cluster etcd1=http://172.31.239.82:12380,etcd2=http://172.31.239.82:22380,etcd3=http://172.31.239.82:32380 \
--initial-cluster-state new
etcd --name etcd3 \
--data-dir /root/etcd/etcd3 \
--initial-advertise-peer-urls http://172.31.239.82:32380 \
--listen-peer-urls http://172.31.239.82:32380 \
--listen-client-urls http://172.31.239.82:32379,http://127.0.0.1:32379 \
--advertise-client-urls http://172.31.239.82:32379 \
--initial-cluster-token token \
--initial-cluster etcd1=http://172.31.239.82:12380,etcd2=http://172.31.239.82:22380,etcd3=http://172.31.239.82:32380 \
--initial-cluster-state new
cd etcd/ssl
etcdctl --endpoints=http://172.31.239.82:12379 member list -w table
etcdctl --endpoints=http://172.31.239.82:12379 endpoint status -w table --cluster
rm -fr /root/etcd/etcd1/*
etcd --name etcd1 \
--data-dir /root/etcd/etcd1 \
--initial-advertise-peer-urls https://172.31.239.82:12380 \
--listen-peer-urls https://172.31.239.82:12380 \
--listen-client-urls https://172.31.239.82:12379,https://127.0.0.1:12379 \
--advertise-client-urls https://172.31.239.82:12379 \
--initial-cluster-token token \
--initial-cluster etcd1=https://172.31.239.82:12380,etcd2=https://172.31.239.82:22380,etcd3=https://172.31.239.82:32380 \
--initial-cluster-state new \
--client-cert-auth \
--peer-client-cert-auth \
--trusted-ca-file "/root/etcd/ssl/ca-etcd.pem" \
--cert-file "/root/etcd/ssl/cert-etcd.pem" \
--key-file "/root/etcd/ssl/cert-etcd-key.pem" \
--peer-trusted-ca-file "/root/etcd/ssl/ca-etcd.pem" \
--peer-cert-file "/root/etcd/ssl/cert-etcd.pem" \
--peer-key-file "/root/etcd/ssl/cert-etcd-key.pem" \
--cipher-suites "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384"
rm -fr /root/etcd/etcd2/*
etcd --name etcd2 \
--data-dir /root/etcd/etcd2 \
--initial-advertise-peer-urls https://172.31.239.82:22380 \
--listen-peer-urls https://172.31.239.82:22380 \
--listen-client-urls https://172.31.239.82:22379,https://127.0.0.1:22379 \
--advertise-client-urls https://172.31.239.82:22379 \
--initial-cluster-token token \
--initial-cluster etcd1=https://172.31.239.82:12380,etcd2=https://172.31.239.82:22380,etcd3=https://172.31.239.82:32380 \
--initial-cluster-state new \
--client-cert-auth \
--peer-client-cert-auth \
--trusted-ca-file "/root/etcd/ssl/ca-etcd.pem" \
--cert-file "/root/etcd/ssl/cert-etcd.pem" \
--key-file "/root/etcd/ssl/cert-etcd-key.pem" \
--peer-trusted-ca-file "/root/etcd/ssl/ca-etcd.pem" \
--peer-cert-file "/root/etcd/ssl/cert-etcd.pem" \
--peer-key-file "/root/etcd/ssl/cert-etcd-key.pem" \
--cipher-suites "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384"
rm -fr /root/etcd/etcd3/*
etcd --name etcd3 \
--data-dir /root/etcd/etcd3 \
--initial-advertise-peer-urls https://172.31.239.82:32380 \
--listen-peer-urls https://172.31.239.82:32380 \
--listen-client-urls https://172.31.239.82:32379,https://127.0.0.1:32379 \
--advertise-client-urls https://172.31.239.82:32379 \
--initial-cluster-token token \
--initial-cluster etcd1=https://172.31.239.82:12380,etcd2=https://172.31.239.82:22380,etcd3=https://172.31.239.82:32380 \
--initial-cluster-state new \
--client-cert-auth \
--peer-client-cert-auth \
--trusted-ca-file "/root/etcd/ssl/ca-etcd.pem" \
--cert-file "/root/etcd/ssl/cert-etcd.pem" \
--key-file "/root/etcd/ssl/cert-etcd-key.pem" \
--peer-trusted-ca-file "/root/etcd/ssl/ca-etcd.pem" \
--peer-cert-file "/root/etcd/ssl/cert-etcd.pem" \
--peer-key-file "/root/etcd/ssl/cert-etcd-key.pem" \
--cipher-suites "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384"
cd etcd/ssl
etcdctl --endpoints=https://172.31.239.82:12379 --cacert=ca-etcd.pem --cert=cert-etcd.pem --key=cert-etcd-key.pem member list -w table
sed -i 's/dl-cdn.alpinelinux.org/a.b.com\/artifactory/g' /etc/apk/repositories
docker run -d -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 mysql:8.0.31
kind delete cluster -n test-cluster
# v1.25.11 v1.26.6 v1.17.3 v1.28.0
kind create cluster --name test-cluster --image kindest/node:v1.28.0 --config cluster-config.yaml
apiVersion: kind.x-k8s.io/v1alpha4
kind: Cluster
nodes:
- role: control-plane
- role: worker
- role: worker
#cloud-config
passwd:
users:
- name: "core"
ssh_authorized_keys:
- "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCtcjZh2+cPHF19Tz9wMDvvilnA2LIfHi15x8SQQGP2y0mUa40K0AysV4+03AFTbbAjGqT288HWF470aNNE8xGUHWUPN0wNZTsiawrQ96fiypefdLkWBYumbEMZm4KFl2oibm+3nM3aWeJUjBz7BGpaXqOdHhpDPloTYsJxAll/tq86hW8k/91pLJEWxkbUCHoUpPB/uXVoPg4qp1VyuWgPz0/GfLfOJXQx6MDcKFogjLbVqBFq/4bdZ3Saz0XQ5uxlphxhQjS6K3UiUCzwGPd1K0MOM3AQ1vC6cdLCrr18TYNbZXgkTCrX4IR1hSsFnNmd7Wy9GGHYcZi1pICKS9IR jude.x.zhu@newegg.com"
- name: "aaa"
password_hash: "$6$brlNJCkW6ictVVVS$cuFNQVDLA7dU/zyQqOZeyKxoppH8hmQheoCQwnZMUDXVlcKDEJp/E15GJqRCqcZsRYwto3LpSAs1SGNr7K6jn."
ssh_authorized_keys:
- "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCtcjZh2+cPHF19Tz9wMDvvilnA2LIfHi15x8SQQGP2y0mUa40K0AysV4+03AFTbbAjGqT288HWF470aNNE8xGUHWUPN0wNZTsiawrQ96fiypefdLkWBYumbEMZm4KFl2oibm+3nM3aWeJUjBz7BGpaXqOdHhpDPloTYsJxAll/tq86hW8k/91pLJEWxkbUCHoUpPB/uXVoPg4qp1VyuWgPz0/GfLfOJXQx6MDcKFogjLbVqBFq/4bdZ3Saz0XQ5uxlphxhQjS6K3UiUCzwGPd1K0MOM3AQ1vC6cdLCrr18TYNbZXgkTCrX4IR1hSsFnNmd7Wy9GGHYcZi1pICKS9IR jude.x.zhu@newegg.com"
groups:
- "sudo"
- "docker"
networkd:
units:
- name: 00-eno.network
contents: |
[Match]
Name=eno*
[Network]
Bond=bond0
- name: 10-bond0.netdev
contents: |
[NetDev]
Name=bond0
Kind=bond
[Bond]
Mode=active-backup
MIIMonitorSec=1
- name: 20-bond0.network
contents: |
[Match]
Name=bond0
[Network]
DNS=172.16.166.111
DNS=172.16.166.112
Address=172.16.164.133/24
Gateway=172.16.164.1
systemd:
units:
- name: containerd.service
dropins:
- name: 10-use-cgroupfs.conf
contents: |
[Service]
Environment=CONTAINERD_CONFIG=/usr/share/containerd/config-cgroupfs.toml
- name: settimezone.service
enabled: true
contents: |
[Unit]
Description=Set the time zone
[Service]
ExecStart=/usr/bin/timedatectl set-timezone America/Los_Angeles
RemainAfterExit=yes
Type=oneshot
[Install]
WantedBy=multi-user.target
- name: systemd-networkd.service
enable: true
- name: update-engine.service
mask: true
- name: locksmithd.service
mask: true
storage:
filesystems:
- name: "OEM"
mount:
device: "/dev/disk/by-label/OEM"
format: "btrfs"
files:
- filesystem: "OEM"
path: "/grub.cfg"
mode: 0644
append: true
contents:
inline: |
set linux_append="$linux_append systemd.unified_cgroup_hierarchy=0 systemd.legacy_systemd_cgroup_controller"
- path: /etc/flatcar-cgroupv1
mode: 0444
- filesystem: "root"
path: "/etc/hostname"
mode: 0644
contents:
inline: e11k8swk13.mercury.corp
- path: /etc/systemd/timesyncd.conf
filesystem: root
mode: 0644
contents:
inline: |
[Time]
NTP=172.16.72.21 172.16.72.22 172.16.72.23
CKS认证考试包括这些一般领域及其在考试中的权重:
Task 1.在现有namespace qa中创建一个名为 backend-sa 的新 ServiceAccount ,确保此 ServiceAccount 不自动挂载 API 凭据。 2. 使用 /cks/sa/pod1.yaml 中的清单文件来创建一个 Pod 3. 最后,清理 namespace qa 中任何未使用的 ServiceAccount
k create sa -n qa backend-sa
k explain serviceaccount #获取不挂载API的字段
k edit sa backend-sa -n qa # 复制进来,值为 false
# 好像要用上面的sa,先edit pod1.yaml 加上 serviceAccountName: backend-sa
k apply -f /cks/sa/pod1.yaml
k get pod -oyaml -n qa|grep serviceAccont: #查看所有使用的sa
k dekete sa -n qa xxx #清理
Context 绑定 Pod 的 ServiceAccount 的 Role授予宽松的权限,完成以下项目减少权限集合: Task: 一个名为 web-pod 的现有 Pod 在 namespace db 中运行,编辑绑定到pod 的 serviceaccount service-account-web 的现有的 Role, 仅允许对 services 类型的资源执行 get 操作,在 namespace db 中创建一个名为 role-2 ,并仅允许对namespace类型的资源执行 delete 操作的新 role。 创建一个名为 role-2-binding 的新 rolebinbing,将创建的 Role 绑定到 Pod 的serviceaccount。 注意: 请勿删除现有 rolebinding 。
k get role -n db
k edit role -n db xxx # 对services执行get操作
k create role role-2 -n db --verb=delete --resource=namespaces
k create rolebinding role-2-binding -n db --role=role-2 --serviceaccount=db:service-account-web
#验证,可以使用 describe 或者 auth can-i
k describe rolebinding role-2-binding -n db
k auth can-i get services -n db --as=system:serviceaccount:db:service-account-web
k auth can-i delete namespaces -n db --as=system:serviceaccount:aba:service-account-web
https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/authorization/
由 kubeadm 创建的cluster 的k8s api 服务器,出于测试目的,临时配置允许未经身份验证和未经授权的访问,授予匿名用户 cluster-admin 权限。
Task: 重新配置 cluster 的 kubernetes api 服务器,以确保只允许经过身份验证和授权的rest请求。 使用授权模式Node,RBAC和准入控制器 NodeRestriction。 删除用户 system:anonymous 的 Clusterrolebinding 来进行清理。
注意: 所有kubectl配置的未授权的访问,必须更改。一旦完成 cluster 的安全加固,kubectl的配置将无法工作,可以使用master节点上的配置文件 /etc/kubernetes/admin.conf,以确保经过身份验证的授权的请求仍然内允许。
# 修改配置文件
--authorization-mode RBAC,Node
--enable-admission-plugins NodeRestriction
k delete clusterRolebinding system:anonymous
https://kubernetes.io/zh-cn/docs/reference/command-line-tools-reference/kube-apiserver/
#切换到master机器上
kubectl get node -owide --kubeconfig=/etc/kubernetes/admin.conf
Task : 使用运行时检测工具来检测Pod tomcat 单个容器中频发生成和执行的异常进程。有两种工具可供使用:
注意: 这些工具只预装在 cluster 的工作节点,不在master 节点。使用工具至少分析 30 秒, 使用过滤器检查生成和执行的进程,将事件写到 /opt/JSR00101/incidents/summary 文件中,其中包含检测的事件。
格式如下: [timestamp],[uid],[processName] 保持工具的原始时间戳格式不变。
注意:确保事件文件存储在集群的工作节点上。
Context
一个默认拒绝(default-deny)的 NetworkPolicy 可避免在未定义任何其他 NetworkPolicy 的 Namespace 中意外公开pod。
Task:
为所有类型为 Ingress + Egress 的流量在 namespace testing 中创建一个名为 denypolicy 的新默认拒绝 NetworkPolicy。
此新的 NetworkPolicy 必须拒绝 namespace testing 中的所有的 Ingress + Egress 流量。
将新创建的默认拒绝 NetworkPolicy 应用与在 namespace testing 中运行的所有的pod。
# 拒绝所有pod
# https://kubernetes.io/zh-cn/docs/concepts/services-networking/network-policies/#default-deny-all-ingress-and-all-egress-traffic
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: denypolicy
namespace: testing
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
# 应用
kubectl apply -f net.yaml
# 检查
kubectl describe networkpolicy -n testing denypolicy
在 cluster 中启用审计日志。为此,请启用日志后端,并确保:
/etc/kubernetes/logpolicy/sample-policy.yaml 提供了基本策略。它仅指定不记录的内容。 注意:基本策略位于 cluster 的 master 节点上。
编辑和扩展基本策略以记录:
此外,添加一个全方位的规则以在 Metadata 级别记录所有其他请求。
注意: 不要忘记应用修改后的策略。
https://kubernetes.io/zh-cn/docs/tasks/debug/debug-cluster/audit/#audit-policy
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
- ReqyestReceived
rules:
- level: RequestResponse
resources:
- group: ""
resources: ["persistentvolumes"]
- level: Request
resources:
- group: ""
resources: ["configmaps"]
namespaces: ["front-apps"]
- level: Metadata
resources:
- group: ""
resources: ["recrets","configmaps"]
- level: Metadata
omitStages:
- "RequestReceived"
备份 kube-apiserver.yaml 文件,然后修改,新增一下内容:
- --audit-log-path: /var/log/kubernetes/audit-logs.txt
- --audit-log-maxage: 10
- --audit-log-maxbackup: 2
- --audit-policy-file=/etc/kubernetes/logpolicy/sample-policy.yaml
volumeMounts:
- mountPath: /var/log/kubernetes
name: audit-log
readOnly: false
- mountPath: /etc/kubernetes/logpolicy
name: audit
readOnly: true
volumes:
- hostPath:
path: /var/log/kubernetes
type: DirectoryOrCreate
name: audit-log
- hostPath:
path: /etc/kubernetes/logpolicy
type: DirectoryOrCreate
name: audit
重启 kubelet
# 重启
systemctl daemon-reload
systemctl restart kubelet
#检查
tail -f /var/log/kubernetes/audit-log.txt
在 namespace istio-system 中获取名为 db1-test 的现有 secret 的内容。 将 username 字段存储在 /cka/sec/user.txt 文件中,并将 Password 字段存储在名为 /cks/sec/pass.txt 文件中。
注意: 不要再一下步骤中使用/修改先前创建的文件,如果需要,可以创建新的临时文件。
在 istio-system namespace 中创建一个名为 db2-test 的新 secret ,内容如下: username: adf password: KvLft
最后,创建一个新的pod,可以通过卷访问secret db2-test:
https://kubernetes.io/zh-cn/docs/concepts/configuration/secret/#restriction-secret-must-exist
k get secret db1-test -n istio-system
mkdir -p /cks/sec
cd /cks/spc
echo 值 |base64 -d > user.txt
echo 值 |base64 -d > pass.txt
cat user.txt
cat pass.txt
k create secret generic db2-test -n istio-system --from-literal=username=adf --from-literal=password=KvLft
搜 secret ,复制模板改要求即可。
分析和编辑给定的 Dockerfile /cks/docker/Dockerfile,并修复在文件中拥有的突出的安全/最佳实践问题的两个指令。
分析和编辑给定清单文件 /cks/docker/deployment.yaml ,并修复在文件中突出的安全/最佳实践问题的两个字段。
注意:请勿删除或者添加配置设置;只需修改现有的配置设置,让两个配置不再有安全问题。
注意: 如果需要非特权用户来执行任何项目,请使用用户ID 65535 的用户 nobody。
只需要修改即可,不需要创建。
Dockerfile 看题目,image的tag不正确,然后需要将 USER root 换成 **USER nobody**
Deployment 文件将 SecurityContext 里面的admin权限删掉:**SYS_ADMIN**,第二个是 pod 的 label标签和 matchLabels 不匹配,修改 pod 的 metadata labels。
该 cluster 使用 containerd 作为 CRI 运行时,containerd 的默认运行时处理程序是runc。 Containerd 已准备好支持额外的运行时处理程序 runsc(gVisor)
Task:
使用名为 runsc 的现有运行时处理程序,创建一个名为 untrusted 的 RuntimeClass。
更新 namespace server 中的所有pod以在 gVisor 上运行。
可以在 /cks/gVisor/rc.yaml 中找到一个模板清单。
# 创建 runtimeclass,官网搜索模板
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
# 用来引用 RuntimeClass 的名字
# RuntimeClass 是一个集群层面的资源
name: untrusted
# 对应的 CRI 配置的名称
handler: runsc
# 查看pod,是否是修改deploy,添加字段并验证
runtimeClassName: untrusted
最佳实践是将容器设计为无状态和不可变的。
Task:检查在 namespace production 中运行的pod,并删除任何非无状态或者非不可变的pod。
使用以下对无状态和不可变的严格解释:
思路:
要求删除非无状态和非不可变的pod,即删除任何形式的特权pod和挂载volume的pod。
# 查询是不是pod控制
k get all -n production
# 查询pod
k get pod -n production
# 每个pod查看yaml,如果满足要求直接删除
k get pod -n production -oyaml xxx
k delete pod -n production xxx
Context
Container Security Context 应在特定 namespace 中修改 Deployment
Task
按照如下要求修改 sec-ns 命名空间中的 Deplyment secdep
k create deploy secdep –image=nginx –replicas=2 –dry-run=client -oyaml -n sec-ns
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: secdep
name: secdep
namespace: sec-ns
spec:
securityContext: # 添加
runAsUser: 30000
replicas: 2
selector:
matchLabels:
app: secdep
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: secdep
spec:
containers:
- image: nginx
name: nginx
resources: {}
securityContext: # 添加,如果有多个容器,每个容器都要添加
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
Task
创建一个名为 pod-restriction 的 NetworkPolicy 来限制对在 namespace dev-team 中运行的 Pod products-service 的访问。
只允许以下 Pod 连接到 Pod products-service
注意:确保应用 NetworkPolicy。 你可以在/cks/net/po.yaml 找到一个模板清单文件。
思路:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: pod-restriction
namespace: dev-team
spec:
podSelector:
matchLabels:
name: products-service
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: qa
- from:
- podSelector:
matchLabels:
environment: testing
namespaceSelector: {}
# apply 和验证
k create -f netpol.yaml
k describe networkpolicy pod-restriction -n dev-team
Task 使用 Trivy 开源容器扫描器检测 namespace kamino 中 Pod 使用的具有严重漏洞的镜像。
查找具有 High 或者 Critical 严重性漏洞的镜像,并删除使用这些镜像的 Pod。
注意:Trivy仅安装在cluster的master节点上,在工作节点上不可使用。 必须切换到cluster的master节点才能使用Trivy.
trivy image --help
trivy image --severity HIGH,CRITICAL image:tag
k get pods --namespace kamino --output=custom-columns="Name:.metadata.name,Img:.spec.containers[*].image"
k delete pod -n kamino xxx
Context
AppArmor 已在 cluster 的工作节点上被启用,一个 AppArmor 配置文件已存在,但尚未被实施。
Task
在 cluster 的工作节点上,实施位于 /etc/apparmor.d/nginx_apparmor 的现有 AppArmor 配置文件。
编辑位于 /home/candidate/KSSK00401/nginx-deploy.yaml 的现有清单文件以应用 AppArmor 配置文件。最后,应用清单文件并创建其中指定的Pod。
准入控制器 是一段代码,它会在请求通过认证和鉴权之后、对象被持久化之前拦截到达 API 服务器的请求。
准入控制器可以执行**验证(Validating)和变更(Mutating)**操作。变更控制器可以根据被其接受的请求更改相关对象;验证控制器则不行。
准入控制器限制创建、删除、修改对象的请求。不会阻止读取(get watch list)对象的请求。
两个阶段。第一阶段,运行变更准入控制器。第二阶段,运行验证准入控制器。
如果两个阶段之一的任何一个控制器拒绝了某请求,则整个请求将立即被拒绝,并向最终用户返回错误。
启用: –enable-admission-plugins=NamespaceLifecycle,LimitRanger …
关闭: –disable-admission-plugins=PodNodeSelector,AlwaysDeny …
运行时安全是在容器运行时通过检测和防止恶意行为来提供主动保护,整个容器生命周期中的最后一道安全屏障。
其核心思想是监控并限制容器中高危的行为,缩小容器进程的能力和权限。
容器本身是利用了 Namespace 和 Cgroup 技术,将容器和宿主机之间的资源进行隔离和限制。
Namespace 是 Linux 提供的一种内核级别的环境隔离功能,主要用途是对容器提供资源的访问隔离。Cgroup 是限制容器对资源的占有。
虽然这种隔离限制从资源层面实现了对容器和宿主机之间的环境独立,宿主机的资源对容器不可见。但是这种方式并没有达到真正意义上的安全隔离,由于容器的内核和宿主内核共享,一旦容器中通过恶意行为进行一些高危的操作权限,
或者是利用内核漏洞,往往就能突破这种资源上的隔离,造成容器逃逸,重新危害到宿主机及上面运行的其他容器。
目前 Linux 内核提供一系列的安全能力可以对这些攻击行为进行有效防护。结合内核安全技术的能力,这些技术的作用范围可以简单地分为两种:一种限制是限制行为本身,另一种则是限制行为作用的对象范围。
在Linux系统中 Root 用户作为超级用户拥有全部的操作权限,以 Root 身份运行容器,相当于将容器资源限制大门的钥匙交给容器本身,这是十分危险的。但是以非 Root 身份在后台运行容器的话,由于缺少权限容器中的应用进程容易处处受限。
为了适用这种复杂的权限需求,Linux 细化了 Root 权限的管控能力,将超级用户的权限分解为细粒度的单元,称为 Capabilities 。例如, CAP_CHOWN 允许用户对文件的 UID 和 GID 进行任意修改,即执行 chown 命令。
几乎所有与超级用户相关的特权都被分解成单独的 Capability,可以分别启用或者禁用。这种权限机制的访问控制,控制容器运行所需的 Capabilities 范围,可以有效切断容器中攻击者的行为操作。即使容器攻击者取得了 Root 权限,由于不能获得主机的完全的操作权限,进一步限制了攻击对宿主机的破坏。
但是基于 Capabilities 的权限访问管理,有时候并不能很好地限制容器的操作系统。例如, SYS_ADMIN 管理了诸多系统调用的访问权限。一旦应用进程因为需要进行 sethostname 这样的操作而在容器中开启了 SYS_ADMIN 组的 Capabilities,那么也就让容器具有了 mount 这类可以挂载系统资源的操作权限,导致容器存在逃逸的风险。
Seccomp (Secure Computing Mode)安全计算模式同样也是一种 Linux 内核提供的安全特性,允许用户使用可配置的策略过滤系统调用,该策略使用 BPF 规则实现,从而使 Seccomp 可以对任意的系统调用及其参数进行过滤。
Namespace 的隔离机制让宿主机上的资源对容器不可见,但是没有限制容器中进程的访问权限,资源访问权限的权利是由 Linux DAC(Discretionary Access Control,自主访问控制,简称 DAC)机制来完成的,主要依赖进程的 uid 和 gid 进行管理。
一旦容器中的攻击者突破 Namespace 的界限,往往就可以对容器外的资源进行访问。而解决这一安全风险的关键就是强制访问控制(Mandatory Access Control)MAC 。其中最熟知的 MAC 访问控制安全模块是 SELinux、AppArmor等。
DAC通常允许授权用户自主改变客体的访问控制属性,可以指定其他用户是否有权访问该客体。然而,DAC 机制只约束了用户、同用户组内的用户、其他用户对文件的可读、可写、可执行权限,这对系统的保护作用非常有限。
Linux 系统中所有内容都是以文件的形式保存和管理的,即一切皆文件。
为了克服 DAC 这种脆弱性,出现了 MAC 机制,其基本原理是利用配置的安全策略来控制对客体的访问,且这种访问不被单个程序和用户所影响。
Security-Enhanced Linux 是由美国国家安全局(NSA)联合其他的安全机构(如SCC公司)共同开发的一套 MAC 安全认证机制。
Selinux 规定了每个对象(程序、文件和进程等)都有一个安全上下文(Security Context),它依附于每个对象身上,包含了许多重要的信息。包括 Selinux 用户、角色、类型和级别等。
管理员可以通过定制安全策略来定义上下文,从而定义哪种对象具有什么权限。当一个对象需要执行某个操作时,系统会按照该对象以及该对象要操作的对象的安全上下文所定制的安全策略来检查相对应的权限。如果全部权限都符合,系统就会允许该操作,否则将阻断这个操作。
在启用了 SELinux 的 Linux 操作系统中,SELinux 并不是取代传统的 DAC 机制。当某个对象需要执行某个操作时,需要先通过 DAC 机制的检测,再由 SELinux 定制的安全策略来检测。如果 DAC 规则拒绝访问,则根本无需使用 SELinux 策略。只有通过 DAC 和 SELinux 的双重权限检查确认之后,才能执行操作。
容器在默认配置下是没有开启 SELinux 功能的,需要管理者修改 Docker 守护进程中的参数配置进行开启。
AppArmor 的目的是希望开发一个比 SELinux 更简单易用的访问控制模块。
RedHat 旗下的Linux发行版都预装提供 SELinux 设置,包括 RHEL、 Centos 、Fedora。而Apparmor 安装在 Debian、Ubuntu等发行版上。
与 SELinux 不同,AppArmor 是作为 DAC 机制的补充,用于限制指定目标程序的资源访问权限,例如是否允许读/写某个特定的目录/文件、打开/读/写网络端口以及是否具备某类 Linux Capabilities 等。为了简单易用,Apparmor使用文件名(路径名)作为安全标签,而SELinux使用文件的 inode 作为安全标签。在文件系统中,只有inode才具有唯一性,因此相比于 SELinux,通过改名等方式,AppArmor 存在被绕过的风险。
不管是 SELinux 还是 AppArmor,其实都是 Linux 安全模块(Linux Security Module)LSM,都是基于 LSM 框架。是一种 Linux 操作系统内核提供的一种安全机制,来完成对进程对 Linux 资源访问权限的判断。LSM是和内核代码一起编译在内核文件中,并且只能在系统启动时就进行初始化。
相较于 LSM 框架,eBPF 提供了一种更加简便安全的方式在 Linux 内核中运行自定义的代码。通过编写内核模块来改变或扩展内核行为,往往需要足够的内核编程知识,并且在编写使用过程中需要十分谨慎,以免因为一些疏忽带来内核代码崩溃的问题,甚至留下攻击者可以利用的漏洞。因此为了保障内核安全性,往往内核模块的开发以及内核新版本的发布,都需要长时间的分析测试。
而 eBPF 程序的开发则不需要通过复杂的内核编译,只需要引入相关结构体的头文件申明,这就给 eBPF 程序的开发降低了难度。同时 eBPF 在安全性保证上提供了一道有效的屏障—— eBPF 验证器:在整个 eBPF 的使用过程中,内核会在加载 eBPF 程序时对 eBPF 程序进行分析验证,保证 eBPF 程序不会造成内核崩溃等问题。
更具有优势的一点是,eBPF 程序是可以动态地从内核中加载或删除,而不像 LSM 模块需要重启系统才能进行模块加载,完全可以做到不打断任何已经存在的进程。
eBPF 提供了一种便利的可基于系统或程序事件高效安全执行特定代码的通用能力,并且它可检测的事件覆盖了系统的各个方面(如下图所示),提供了丰富的可观察信息,甚至在 Linux 5.7 版本后 eBPF 程序可以插入 LSM hook 点。
对于容器环境来说,所有运行在一台机器上的容器都和主机共享内核,内核了解主机上运行的所有应用代码。eBPF 的 Hook 点可以说是遍布内核的各个角落,这对于容器安全的检测和防护是很大的助力,方便了对容器中正在进行的操作的分析和判断,近些年来有许多利用这项新技术来解决一些容器安全问题的工具。
当前这类安全上的工具主要可以分为两类:一类是确保网络活动的安全,eBPF 最初就是用于网络数据包过滤的技术,可以在网络驱动中尽可能早的位置提供最优的数据包处理能力,过滤并丢弃恶意或非预期的流量以及防范 DDOS 攻击等。而另一类则是检测恶意行为,确保应用程序在运行时的行为安全。例如利用 eBPF 同样可以在系统调用的入口处插入检测过滤程序,但是相比于 Seccomp 更强大的地方在于,eBPF 程序可以对其中的指针参数进行解引用,这也就方便了更进一步的行为分析。
下表列举了当前比较有名的基于 eBPF 开发的安全工具:
当前云原生的技术大多数被服务于 Web 应用的相关业务,此类业务往往把性能要求放在较为靠前的位置,对于设备上选用的安全加固技术都会有性能方面的考量。而 eBPF 的出现为容器安全带来了一种能够动态、轻量、无感知的提升防御能力的方式。其全面的观测能力,可以轻松地监控到容器安全可观测性的四个黄金信号:进程执行、网络套接字、文件访问和七层网络身份,保证了安全检测的全面性。
在未来的发展过程中,eBPF 的相关应用将是容器运行时安全最重要的助力。同时,不可忽视的是对低版本内核系统上的安全防护,目前并在未来很长的一段时间内,大多数的主机服务器上仍然将运行着 Linux 3.* 以及 4.* 的系统。对于这些系统来说,Seccomp、LSM 模块等内核安全机制依旧是保障容器安全不可或缺的部分。