零、背景
最近在学习K8S 安全相关的内容,本文是K8S 安装的记录,也是对其概念的学习。注意在尝试自行安装的过程,最好每执行一步就对照其概念和组件的介绍 看一下,深入理解每个部分的作用,才可理解其出现安全问题的危害。
一、安装
1.1 安装docker
参考:https://docs.docker.com/engine/install/#server
或者
参考:https://www.runoob.com/docker/centos-docker-install.html
配置 Docker 守护程序,尤其是使用 systemd 来管理容器的cgroup。
sudo mkdir /etc/docker
cat <<EOF | sudo tee /etc/docker/daemon.json
{
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"storage-driver": "overlay2"
}
EOF
为什么要更换cgroup:https://www.cnblogs.com/architectforest/p/12988488.html
1.2 安装 kubeadm
kubeadm 是一个工具,用来管理K8S集群。可以翻墙的话就参考官方文档,不可以的话就使用aliyun 的源,替换 gpgkey 即可。
cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-\$basearch enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg exclude=kubelet kubeadm kubectl EOF # 将 SELinux 设置为 permissive 模式(相当于将其禁用) sudo setenforce 0 sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config sudo yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes sudo systemctl enable --now kubelet
baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64 gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
1.3 创建K8S 集群
然后使用kubeadm初始化集群,初始化集群实际是从google 拉取K8S镜像,但是国内无法访问,可通过以下三步完成初始化。
1、查看需要的镜像列表
[root@server ~]# kubeadm config images list k8s.gcr.io/kube-apiserver:v1.21.0 k8s.gcr.io/kube-controller-manager:v1.21.0 k8s.gcr.io/kube-scheduler:v1.21.0 k8s.gcr.io/kube-proxy:v1.21.0 k8s.gcr.io/pause:3.4.1 k8s.gcr.io/etcd:3.4.13-0 k8s.gcr.io/coredns/coredns:v1.8.0
2、从国内镜像源拉取镜像
根据上面的输出结果,替换下面的镜像列表,然后直接在shell 里执行
images=( # 下面的镜像应该去除"k8s.gcr.io/"的前缀,版本换成上面获取到的版本
kube-apiserver:v1.12.1
kube-controller-manager:v1.12.1
kube-scheduler:v1.12.1
kube-proxy:v1.12.1
pause:3.1
etcd:3.2.24
coredns:1.2.2
)
for imageName in ${images[@]} ; do
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/$imageName
docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/$imageName k8s.gcr.io/$imageName
docker rmi registry.cn-hangzhou.aliyuncs.com/google_containers/$imageName
done
3、执行初始化命令
注意这个IP网段跟CNI 网络插件有关
kubeadm init --pod-network-cidr=10.244.0.0/16
执行完上述命令后会输出这样一个语句,直接执行,此操作是将Master 节点加入集群
kubeadm join 10.xxx.xxx.10:6443 --token kvomhr.xxx --discovery-token-ca-cert-hash sha256:xxx init
4、设置容器网络
容器网络需要通过pod网络插件部署,网络插件就是一个符合容器网络接口(Container Network Interface,CNI)的插件,常见的有calico、flannel等;也可以将CNI理解成一个协议,calico、flannel是实现这些协议的网络解决方案。
这里我们使用flannel 网络,首先在这里下载配置文件,https://github.com/flannel-io/flannel/edit/master/Documentation/kube-flannel.yml,然后执行
kubectl apply -f kube-flannel.yml
然后执行如下命令,表示Master 也可以创建pod
kubectl taint nodes --all node-role.kubernetes.io/master-
然后就可以看到已经创建的容器和Pod
[root@server ~]# kubectl get pods --all-namespaces NAMESPACE NAME READY STATUS RESTARTS AGE default centos-c58b5b56d-7wvn9 1/1 Running 0 2d22h kube-system coredns-558bd4d5db-nhd29 1/1 Running 0 6d21h kube-system coredns-558bd4d5db-wmtvw 1/1 Running 0 6d21h kube-system etcd-server 1/1 Running 0 6d21h kube-system kube-apiserver-server 1/1 Running 0 6d21h kube-system kube-controller-manager-server 1/1 Running 0 5d22h kube-system kube-flannel-ds-gqxd5 1/1 Running 0 6d21h kube-system kube-proxy-rlxfs 1/1 Running 0 6d21h kube-system kube-scheduler-server 1/1 Running 0 5d22h kube-system kubernetes-dashboard-65ff5d4cc8-cgdjp 1/1 Running 0 2d1h
可以看到相关容器也已经开启
[root@server ~]# docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9092f4ef5663 f9aed6605b81 "/dashboard --insecu…" 2 days ago Up 2 days k8s_kubernetes-dashboard_kubernetes-dashboard-65ff5d4cc8-cgdjp_kube-system_4e42c9b2-b4ab-4168-a6ca-58deb2cd1901_0 ... ... c2a23054253d k8s.gcr.io/pause:3.4.1 "/pause" 6 days ago Up 6 days k8s_POD_etcd-server_kube-system_a280226d84c4e11d0949c6c403632b9c_0
查看一下服务状态 kubectl get cs,出现以下错误
打开以下文件
vim /etc/kubernetes/manifests/kube-scheduler.yaml
vim /etc/kubernetes/manifests/kube-controller-manager.yaml
删掉此行代码
--port=0(表示禁用非安全端口)
重启服务
systemctl restart kubelet
状态正常
kubectl cluster-info
1.4 安装K8S dashboard
通过如下命令拉取部署配置文件
wget https://raw.githubusercontent.com/kubernetes/dashboard/v1.10.1/src/deploy/recommended/kubernetes-dashboard.yaml kubectl apply -f kubernetes-dashboard.yaml # 如果镜像无法拉取,就通过如下命令显示的拉取镜像 docker pull mirrorgooglecontainers/kubernetes-dashboard-amd64:v1.10.1 docker tag mirrorgooglecontainers/kubernetes-dashboard-amd64:v1.10.1 k8s.gcr.io/kubernetes-dashboard-amd64:v1.10.1
然后查看Pod 启动情况,可以看到dashboard 已经成功运行。
[root@server ~]# kubectl get pod -n kube-system NAME READY STATUS RESTARTS AGE coredns-558bd4d5db-nhd29 1/1 Running 0 6d21h coredns-558bd4d5db-wmtvw 1/1 Running 0 6d21h etcd-server 1/1 Running 0 6d21h kube-apiserve-server 1/1 Running 0 6d21h kube-controller-manager-server 1/1 Running 0 5d23h kube-flannel-ds-gqxd5 1/1 Running 0 6d21h kube-proxy-rlxfs 1/1 Running 0 6d21h kube-scheduler-server 1/1 Running 0 5d23h kubernetes-dashboard-65ff5d4cc8-cgdjp 1/1 Running 0 2d1h
通过如下命令即可查看dashboard 对外的端口,
[root@server ~]#kubectl get service -n kube-system NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 6d21h kubernetes-dashboard NodePort 10.97.5.206 <none> 443:32233/TCP 2d1h
这里的TYPE 必须是NodePort 才能被外部访问,默认是cluserIP,可以通过下面两个命令修改
# 直接修改 kubectl patch svc kubernetes-dashboard -p '{"spec":{"type":"NodePort"}}' -n kube-system # 编辑 Service 修改 kubectl edit service kubernetes-dashboard -n kube-system
二、使用K8S
2.1 创建一个Pod
首选创建一个development 文件
apiVersion: apps/v1 # K8S对应的API版本 kind: Deployment # 对应的类型 metadata: name: centos labels: name: centos spec: replicas: 1 # 镜像副本数量 selector: matchLabels: app: centos template: metadata: labels: # 容器的标签 可和service关联 app: centos spec: containers: - name: centos # 容器名和镜像 image: centos:latest imagePullPolicy: IfNotPresent command: ["/bin/bash","-ce","while true;do echo hello;sleep 1;done"]
然后执行
kubectl create -f centos.yaml
系统会自动创建一个development,并根据这个development 创建一个Pod,并在Pod 中启动容器。注意,如果是本地镜像imagePullPolicy 的值必须是 IfNotPresent 或者 Never,否则K8S 只会从外部仓库地址拉取镜像。另外如果容器中不是一个可持续运行的进程,需要手动添加一些操作,使其不会自动退出,这里我们加入 while true;do echo hello;sleep 1;done
这条命令达到这种效果。然后就可以看到Pod 启动了。
[root@server deployment]# kubectl create -f centos.yaml deployment.apps/centos created [root@server deployment]# kubectl get pods NAME READY STATUS RESTARTS AGE centos-c58b5b56d-7wvn9 1/1 Running 0 11d
当需要更新部署时,修改yaml 部署文件,重新执行命令即可。
2.2 创建一个Service
Pod 创建成功之后,说明Docker 已经运行起来了,我们的系统环境已经OK了。在部署完程序之后,为了能够让程序被外部访问到还要设置服务发现。在Docker 中服务发现是通过端口转发实现的,我们可以将一个Docker 容器的端口映射到主机的端口,然后通过主机IP+主机端口的形式进行访问。K8S中引入了Service 的概念,通过Service 实现后端服务发现和负载均衡。
使用如下命令,将自动从已有的部署中创建Service
kubectl expose deployment/centos
通过如下命令可以查看创建的Service 的信息
kubectl describe svc centos
这样我们就获得了一个Service ,并且可以看到Service IP地址和Endpoints IP地址,Service IP是不会变的,而Endpoints IP 可能随着服务迁移或重启而发生变化。
[root@server deployment]# kubectl describe svc centos Name: centos Namespace: default Labels: name=centos Annotations: <none> Selector: app=centos Type: NodePort IP Family Policy: SingleStack IP Families: IPv4 IP: 10.101.164.49 IPs: 10.101.164.49 Port: <unset> 22/TCP TargetPort: 22/TCP NodePort: <unset> 30022/TCP Endpoints: 10.244.0.19:22 Session Affinity: None External Traffic Policy: Cluster Events: <none>
但是服务仍然是在 K8S 环境内的,想要被外部访问需要在Service 中配置对外暴露的地址,有两种方式,NodePort和LoadBalancer。
NodePort一般是指在本机对外的IP 上暴露的端口,如下所示,通过
kubectl edit svc/centos
编辑Service,type=NodePort,nodePort: 30022,则可以通过宿主机的30022端口访问内部容器的22 端口。
apiVersion: v1 kind: Service metadata: creationTimestamp: "2021-04-25T09:49:52Z" labels: name: centos name: centos namespace: default resourceVersion: "414991" uid: c286621c-5790-4821-b6b6-3c1a5eaf704a spec: clusterIP: 10.101.164.49 clusterIPs: - 10.101.164.49 externalTrafficPolicy: Cluster ipFamilies: - IPv4 ipFamilyPolicy: SingleStack ports: - nodePort: 30022 port: 22 protocol: TCP targetPort: 22 selector: app: centos sessionAffinity: None type: NodePort status: loadBalancer: {}
然后查看service 就可以看到端口映射关系
[root@server deployment]# kubectl get service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE centos NodePort 10.101.164.49 <none> 22:30022/TCP 11d
LoadBalancer 是云上环境的暴露方式,具体使用方式要看各家云厂商的方案,基本原理就是暴露到一个云IP上。以上是我们使用K8S 环境的一个例子,同理可以部署其他容器并对外提供服务。
三、K8S 鉴权
K8S 属于底层的系统,它可能管理着成百上千的业务服务,因此它的鉴权认证非常重要,不能由外部人员随意访问。
3.1 dashboard 鉴权
前面我们已经成功创建了dashboard,并能够在浏览器中打开它,现在我们需要为登录面板创建一个token 来保证的它的安全。首先创建一个serviceaccount
kubectl create serviceaccount dashboard-admin -n kube-system
接下来需要通过RoleBinding把dashboard-admin这个serviceaccount和集群管理员绑定起来。不然这个serviceaccount不能通过RBAC的检查。dashboard部署完后,登录进来期望借助于dashboard做什么事情,假如要做整个集群的管理,也就是说该serviceaccount应该具有整个集群的访问权限,那么就应该通过ClusterRoleBinding把这个serviceaccount和cluster-admin角色绑定在一起。
kubectl create clusterrolebinding dashboard-cluster-admin --clusterrole=cluster-admin --serviceaccount=kube-system:dashboard-admin
绑定完成后,我们去获取对应的serviceaccount的secret信息。并且通过这个secret就可以访问集群了。
[root@server ~]# kubectl get secret -n kube-system | grep dashboard-admin dashboard-admin-token-nsvsq kubernetes.io/service-account-token 3 2d [root@server ~]# kubectl describe secret dashboard-admin-token-nsvsq -n kube-system Name: dashboard-admin-token-nsvsq Namespace: kube-system Labels: <none> Annotations: kubernetes.io/service-account.name: dashboard-admin kubernetes.io/service-account.uid: 162ccd4c-c1dd-48ae-b092-7b905cfb43ad Type: kubernetes.io/service-account-token Data ==== token: eyJhbGciOiJSU****************************bwIZhmH0SPvOY1W3ScaKAnXApDCmaN15hSL-q7avbSu3vSNR9tOzvR1dH5SDUqJCEEl8bUwpTvrurU9oLV-oP38rThr33fKZtqpybsVda1tWfXwRjw7fY6kFt1YgKrztEVTEg2c84qMo8vT2y9tMcq4X4vlN6aNcJQRysnGu9NXc9x6grkH7gvCVYbddAOj_LdvsxjUfCUmAY77XdtVcTkSGx5StdAqvZjUchn0eBc3LQvGpdQ ca.crt: 1066 bytes namespace: 11 bytes
然后就可以看到token
[root@server ~]# kubectl describe secret dashboard-admin-token-nsvsq -n kube-system Name: dashboard-admin-token-nsvsq Namespace: kube-system Labels: <none> Annotations: kubernetes.io/service-account.name: dashboard-admin kubernetes.io/service-account.uid: 162ccd4c-c1dd-48ae-b092-7b905cfb43ad Type: kubernetes.io/service-account-token Data ==== ca.crt: 1066 bytes namespace: 11 bytes token: eyJhbGciOiJSU****************************c84qMo8vT2y9Xc9x6SGx5StdAqvZjUchn0eBc3LQvGpdQ
在dashboard 登录界面选择token,输入然后就可以登录了。
3.2 API Service 鉴权
K8S API Service 默认会开启两个API端口,8080 和 6443,8080是仅能本地访问的端口,无鉴权;6443是可从外部访问的端口,默认需要认证才可访问。API Service 有三种级别的客户端认证方式
- HTTPS 证书认证:基于CA根证书签名的双向数字证书认证方式
- HTTP Token认证:通过一个Token来识别合法用户
- HTTP Base认证:通过用户名+密码的认证方式
通常使用第二种比较方便,HTTP Token 认证就是在HTTP Header 中添加认证头
Authorization: Bearer $TOKEN
这里的token 可以使用dashboard 的token
token: eyJhbGciOiJSU****************************c84qMo8vT2y9Xc9x6SGx5StdAqvZjUchn0eBc3LQvGpdQ
通过如下方式访问即可
curl -H "Authorization: Bearer eyJhbGciOiJSU****************************c84qMo8vT2y9Xc9x6SGx5StdAqvZjUchn0eBc3LQvGpdQ" --insecure https://127.0.0.1:6443/ { "paths": [ "/.well-known/openid-configuration", "/api", "/api/v1", ... ... "/readyz/poststarthook/start-kube-aggregator-informers", "/readyz/poststarthook/start-kube-apiserver-admission-initializer", "/readyz/shutdown", "/version" ] }
四、网络架构
4.1 层级概念
在了解网络如何通信之前,我们要先学习K8S 各个组件之间的关系,由小到大如下所示。
4.1.1 Container
Container(容器)是一种轻量级的虚拟化技术,使用容器可以方便的启动程序或运行服务。在K8S 中容器是实际运行服务的环境,常见的容器技术是docker,除此之外还有containerd、CRI-O等。
4.1.2 Pod
Pod 是K8S中最小的控制单元,一个Pod 有一个或多个容器组成,一个Pod 通常由一个deployment(部署,yaml 格式的配置文件,告诉K8S 如何创建这个Pod)来描述。一个Pod 通常表示一个完整的服务,内部的各个Container 维持着服务的运行。
4.1.3 Node
Node 就是真正运行K8S 的机器,它可以是物理机或者虚拟机,通常也称之为宿主机。Node 机器上必须安装Docker 和kubelet 服务,以满足K8S 的运行。一个K8S 集群中会区分Master 节点和 普通的Node 节点,为主从关系,当然也可以放到一个宿主机上。一个Node 可以启动多个Pod,这与Node 配置有关。
4.1.4 Service
Service 是用于负载均衡和服务发现组件,对外外部访问请求,实际流量是先找到Service,然后由Service 将流量转发到具体的Pod 并做好多个Pod 副本之间的负载均衡。
4.1.5 Namespace
Namespace 是一组资源和对象的抽象集合,它不是具体的组件,只是起到逻辑划分作用,与操作系统、代码空间中的Namespace 可以类似理解。
4.2 网络关系
在K8S 中Pod 是最小单元,每个Pod 有一个IP地址,每个Pod 中会有多个容器,其中一个是系统自动启动的Pause 容器。Pause 容器与Pod 一一对应,起枢纽作用,负责在Pod内连接各个Container。使得一个Pod 内的所有容器可以共享网络栈和挂载卷,可以直接进行数据交换和通信,如果进入一个Pod 内的不同容器,会发现他们的IP 地址是一样的。
Pod 的IP 是Docker 的网卡分配的,不是一个固定的值,当重启或生成新的Pod 时可能会发生变化。因此Pod IP不能作为对外的服务IP,Service IP 时一个固定的值,它会根据label 属性将请求转发到对应的Pod IP上。但Service IP 也不是一个真实的IP,外部无法路由到它。
Node IP 时宿主机的IP,是一个真实的可以被外部访问的IP,需要将Node IP 通过端口映射的方式的关联到Service IP上,因此容器、Pod、Node 是如下的包含关系。Request –> Node –> Service –> Pod –> Container。
参考文献
- https://blog.csdn.net/csdn_welearn/article/details/101107426
- https://blog.csdn.net/textdemo123/article/details/100042251
- https://jkzhao.github.io/2019/09/12/Kubernetes-dashboard%E8%AE%A4%E8%AF%81%E5%8F%8A%E5%88%86%E7%BA%A7%E6%8E%88%E6%9D%83/
- https://kubernetes.io/zh/docs/concepts/overview/components/
- https://network.51cto.com/art/202007/620287.htm