K8S 入门

零、背景

最近在学习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 有三种级别的客户端认证方式

  1. HTTPS 证书认证:基于CA根证书签名的双向数字证书认证方式
  2. HTTP Token认证:通过一个Token来识别合法用户
  3. 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

 

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注