0%

初识 k8s,新时代的宠儿

关于 Kubernetes 系列其他文章的传送门

  1. 《 部署 Kubernetes 也能如此简单吗? 》

  2. 《 初识 k8s,新时代的宠儿 》     您当前所在位置

  3. 《 深度学习 Pod 》

  4. 《 学会这 5 种 Pod 控制器,搞定发布、更新、回滚,从此告别人肉运维!! 》

  5. 《 k8s 中的网络入口 – Service 资源管理 》

  6. 《 k8s 中的 7 层调度 – Ingress 》

  7. 《 k8s 中的存储系统 – Volumes 》

  8. 《 k8s 中强大的配置中心 – ConfigMap 》

  9. 《 k8s 中的部署神器 – Statefulset 控制器 》

  10. 《 k8s 中的安全守护神 – 你不知道不代表它不在 》

  11. 《 k8s 中的图形接口 – Dashboard 》

  12. 《 k8s 中的网络插件 – flannel 和 Calico 》

  13. 《 k8s 中的调度器 – 亲和性、污点和容忍性 》

  14. 《 k8s 中对资源的监控 – 资源指标、Prometheus、弹性伸缩器 HPA 》

  15. 《 k8s 中的云原生应用管理利器 – Helm 》

  16. 《 理解 k8s 高可用,让你的集群稳如泰山 》



logo

一、Kubernetes 中的一些重要的概念

3 个控制平面

  • API Server:监听在 Master 中 node 网络6443 套接字上,提供 https 接入;早期还提供 8080 以供 http 使用,后来被取消;
    • 用户认证:采用双向认证,客户端也要提供证书。并且是由信任的 CA 颁发的,在部署 k8s 时,在 /etc/kubernetes/pki 目录下自动创建了 CA 的相关证书。其中 ca.crt 就是 CA 的私钥。
  • Scheduler:负责将创建的 Pod 调度到某个节点运行;
  • Controller:调度完成后,由 Controller 确保有精确数量的 Pod 资源在正常运行;

Node 节点的重要功能

  • 运行 Pod;
  • kube-proxy 在 Node 节点上的 Ipvs 或 iptables 中创建 Services 的规则;

k8s 中重要的组件

最重要的一些基础组件,Pod、Pod Controller、Service 之间的关系:

  • 由 Pod Controller 创建 Pod:
    • 首先明确:Deployment 是一种类型。向一个 Deployment 类型的属性赋值创建出一个具体的 Pod Controller,再由 Controller 创建出 Pod。
    • Service 也是一种类型,通常需要通过赋值的方式来实例化。假设为 Service 的属性赋值,创建出一个特定的 Service 实例叫 nginx-svc,这个实例就能关联到 Pod 上,为 Pod 中的 nginx 调度用户请求。

k8s 中的资源有两个级别

  • 集群级别;
  • 名称空间级别;

二、一些基础命令的使用方法

客户端命令

kubectl 是客户端,它连接到 API Server 来控制 k8s 集群:

  • get nodes:显示主机信息;属于集群级别的资源;
  • get ns:显示名称空间的信息;
    • k8s 部署后默认会创建 3 个名称空间:
      • default:默认用于运行没指定名称空间的 Pod,
      • kube-public:用于运行公共的 Pod,任何人都可以访问;
      • kube-system:专门用于运行系统级的 Pod;
  • get pods:显示 Pod 的相关信息;
    • -n 显示指定名称空间中的 Pod;
    • -o wide:类似 ls,显示长格式信息;
    • -w:代表 watch,动态显示 Pod 的状态。

比如下面这样,就是查看在 kube-system 的名称空间内,有哪些 deploy 类型的控制器。

1
kubectl get deploy -n kube-system

查询资源

先指定资源的类型,再指定要查询的名称,以 ns 资源类型为例:

  • kubectl describe ns/default:显示指定名称空间的状态信息;
  • kubectl get ns:查询所有名称空间的信息;
  • kubectl get ns/default:查询指定名称空间的信息;
  • kubectl get ns/default -o [ yaml | json ]:查询指定名称空间的详细信息,并以指定格式显示。

创建资源

kubectl api-resources查看可用的资源类型;全称和简格式都可以使用

使用 kubectl create 可以创建一个新的资源,要先指定资源的类型,再指定名称;

常用的资源类型有以下:

  • clusterrole
  • clusterrolebinding
  • configmap
  • cronjob
  • deployment
  • job
  • namespace
  • poddisruptionbudget
  • priorityclass
  • quota
  • role
  • rolebinding
  • secret
  • service
  • serviceaccount

创建和删除名称空间

创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
~]# kubectl create namespace develop-1
namespace/develop-1 created

~]# kubectl create namespace testing-1
namespace/testing-1 created

~]# kubectl get ns
NAME STATUS AGE
default Active 4h36m
develop-1 Active 13s
kube-node-lease Active 4h36m
kube-public Active 4h36m
kube-system Active 4h36m
testing-1 Active 7s

删除 ns 同理,有两种格式可以使用:

  • kubectl delete namespace dev-1:一次只能删除一个;
  • kubectl delete namespace/dev-1 namespace/test-1 namespace/run-1:一次能删除多个;

注意:删除名称空间会一并删除内部的所有资源,很危险;

创建 Pod 控制器

创建一个名为 ngx-dep 的控制器,使用指定的 nginx 镜像:

1
kubectl create deploy ngx-dep --image=nginx:1.14-alpine

使用 kubectl get all 查看查看 default 名称空间下的所有资源。

如果把 Pod 删除,deploy 控制器会自动再创建一个 Pod 出来。

创建 Service

创建一个 ClusterIP 类型的 Service 并关联至 Pod,同时指定映射 80 端口;

并且可以使用 --clusterip 指定 IP,如不指定则随机;

1
kubectl create service clusterip ngx-dep --tcp=80:80

注意:定义 Service 的名字时,要和后端的 Pod 控制器名称一致,才会自动关联映射

使用 kubectl describe service/ngx-dep 可以查询 service IP 和 pod IP 的映射关系。

如果 Pod 被重建后 IP 发生了变化,那么这个 service 就会自动和新的 Pod IP 绑定。

思考:IP 变化后,如何访问?

当 service 自己被重建后,IP 会发生变化,那么客户端该如何访问呢?
在 k8s 内部有一个 DNS,在 DNS 中记录着 “域名 -> service IP” 的对应关系,当 service 被重建,IP 发生变化时,这个记录中的对应关系也会跟着变化,那么客户端只要使用这个域名访问就可以了。

客户端把 DNS 指向 k8s 内部的 DNS 地址,即可解析域名。
查看 DNS 地址的方法:

  • 使用 kubectl get svc -n kube-system,找到 DNS 的 Service,名字为 kube-dns,就可以看到 IP 地址了。

域名的格式是:ngx-dep.default.svc.cluster.local.

  • 含义是:service名称.service所在的名称空间.资源的类型(固定后缀).当前k8s所在的域名.,注意最后有 .

在部署 k8s 时使用 --service-dns-domain string 可以指定集群域名,默认使用 cluster.local

Pod 扩、缩容

扩容

先创建一个 Pod 和对应的 svc:

1
2
kubectl create deploy myapp --image=ikubernetes/myapp:v1
kubectl create svc clusterip myapp --tcp=80:80

将刚才创建的 myapp 扩容为 3 个,并指定控制器类型为 deployment:

1
kubectl scale --replicas=3 deployment myapp

查看结果:

1
2
3
4
5
-> # kubectl get pods -o wide  
NAME READY STATUS RESTARTS AGE IP NODE
myapp-5c6976696c-fwfpz 1/1 Running 0 6m2s 10.244.2.3 node2.monster.com
myapp-5c6976696c-mwp7w 1/1 Running 0 6m2s 10.244.1.2 node1.monster.com
myapp-5c6976696c-p7lf2 1/1 Running 0 36m 10.244.3.3 node3.monster.com

并且 svc 中也添加了对应的映射关系:

1
2
3
4
5
6
7
8
9
10
11
12
13
-> # kubectl describe svc/myapp
Name: myapp
Namespace: default
Labels: app=myapp
Annotations: <none>
Selector: app=myapp
Type: ClusterIP
IP: 10.110.234.135
Port: 80-80 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.1.2:80,10.244.2.3:80,10.244.3.3:80
Session Affinity: None
Events: <none>

接下来通过域名访问 svc 对应的域名时,会有负载均衡的效果,默认的调度方式是随机:

1
2
3
4
5
6
7
8
9
10
-> # curl myapp.default.svc.cluster.local/hostname.html
myapp-5c6976696c-p7lf2
-> # curl myapp.default.svc.cluster.local/hostname.html
myapp-5c6976696c-mwp7w
-> # curl myapp.default.svc.cluster.local/hostname.html
myapp-5c6976696c-fwfpz
-> # curl myapp.default.svc.cluster.local/hostname.html
myapp-5c6976696c-mwp7w
-> # curl myapp.default.svc.cluster.local/hostname.html
myapp-5c6976696c-fwfpz

缩容

缩容的方法就是把扩容时指定的数量减小即可,控制器会自动删除多余的,做到精准控制 Pod 的数量:

1
2
3
kubectl scale --replicas=3 deployment myapp

kubectl scale --replicas=2 deployment myapp

三、API Server

API 相关的基础概念

API 接口中的资源被分成多个逻辑组合:

  • 通常情况下,相关的类型会被存放在同一个组合内,被称为 API Group
    • 使用 kubectl api-versions 可以查看有多少群组;
    • 其中 V1 是核心存储,所有核心的组件都在这个组中;
  • 每个组中的资源可以独立演进,实现分而治之;
  • 每个组还可以多版本并存;

绝大部分资源都遵循相同的 资源对象配置格式,有 5 个一级字段:

  • kind:用于标识对象所属的资源类型;
  • apiVersion:指定使用哪个 API 群组以及版本;
  • metadata:属于嵌套字段,保存着资源的元数据,包括名称、所属的名称空间、标签、创建时间等等;
  • spec:定义资源的规范,用户期望达到的状态;
  • status:资源的当前状态,用户不能手动定义,由 k8s 自行维护。

和解循环(Reconciliation Loop)

Controller(控制器)会周期性的检查 status 是否符合 spec 中的状态,如不符合将运行合适的操作,使 status 中的状态无限接近 spec 的状态。

资源对象的管理方式

使用 kubectl 管理资源,命令可分为三类:

1. 陈述式命令(Imperative Commands)

告诉对方要做什么,至于怎么做不用管。

  • deleteget 之类的命令;

2. 陈述式对象配置(Imperative Object Configuration)

要按照指定的配置文件中定义的具体细节创建资源。

  • 使用 kubectl create -f FILE.yaml 指定一个 yaml 格式的资源配置文件;
    • 相同名字的资源只能创建一次,再次创建前需要先删除;
    • 删除可使用 kubectl delete -f FILE.yaml 即可删除;

3. 声明式对象配置(Declarative Object Configuration)

要按照指定的配置文件中定义的具体细节创建资源。建议使用

  • 使用 kubectl apply -f FILE.yaml 指定一个 yaml 格式的资源配置文件。

所不同的是

  • 相同名字的资源可以反复创建,还可以指定一个目录,将目录下所有配置文件的资源全部创建。

清单文件

清单文件可以根据 官网中 API 的参考说明 自己编写,也可以从现有的资源直接导出为模板。

比如将 coredns 的 Pod 资源配置文件导出的方法:

1
`~]# kubectl get pod -n kube-system coredns-5644d7b6d9-2khq8 -o yaml --export > /root/pod-demo.yaml`
  • -o yaml:指定显示为 yaml 格式;
  • --export:导出为模板使用;

API 查询工具 explain

k8s 内置了一个工具 explain,可以查询指定资源类型的配置参数,语法为:

1
kubectl explain  <type>.<fieldName>[.<fieldName>]
  • 比如想查看 Pods 可用参数,使用 kubectl explain pods 即可列出;
  • 如果再想查看 Pods 下的 kind 字段中还有哪些参数可用,则使用 . 递归,比如 kubectl explain pods.kind

四、手动编写资源配置清单

关于拉取镜像的参数

  • imagePullPolicy:定义默认拉取镜像的方式,有三个可用值:
    • Always:代表无论本地是否有镜像,都去网上的仓库拉取;
    • Never:代表如果本地没有镜像,就不启动;
    • IfNotPresent:代表如果目标节点有镜像,就使用,否则就拉取;

而针对不同的镜像,标签不同,默认值也不一样:

  • 如果镜像的标签是 latest,默认值则为 always,因为本地的 latest 版本不一定是互联网上最新的;
  • 否则默认值就为 ifnotpresent

创建自主式 Pod

自主式的 Pod 不归 Pod 控制器管理,生产环境不建议这么做,仅为了理解 Pod 而做演示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
~]# vim /root/pod-nginx.yaml

apiVersion: v1
kind: Pod # 直接创建为 Pod 类型,不受控制器管理
metadata:
name: pod-demo # pod 名称
namespace: default # 在哪个 ns 中
spec:
containers:
- image: nginx:latest # 要拉取的镜像的标签
imagePullPolicy: Always # 定义默认拉取镜像的方式
name: nginx # 自己定义容器的名字
dnsPolicy: ClusterFirst # 定义默认的 DNS 策略
enableServiceLinks: true # 是否允许 svc 引用
nodeName: node2.monster.com # 指定此 Pod 被绑定到哪个节点,不指则调度
priority: 0 # Pod 的优先级,类似进程的优先级
restartPolicy: Always # 如果 Pod 宕机怎么处理,Always 为一旦宕机就重启
schedulerName: default-scheduler # 使用哪个调度器来调度
securityContext: {} # 定义安全上下文

而后创建自主式 Pod:

1
~]# kubectl apply -f /root/pod-nginx.yaml 

在一个 Pod 内启动两个容器的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
~]# vim /root/pod-2.yaml

apiVersion: v1
kind: Pod
metadata:
name: pod-2
namespace: default
spec:
containers:
- image: nginx:latest # 指定第一个容器的镜像
name: nginx
- image: busybox:latest # 指定第二个容器的镜像
name: bbox
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c","sleep 86400"] # 定义容器启动后要执行的命令;
# busybox 启动后默认执行 shell,这么写可防止反复的刚启动就停止

而后创建 Pod:

1
~]# kubectl apply -f /root/pod-2.yaml 

进入容器的交互式接口

一个 Pod 内有两个容器时,使用 -c 来指定容器。如只有一个容器,-c 可省略。

1
2
3
4
~]# kubectl exec pod-2 -c bbox -n default -it -- /bin/sh

~]# netstat -tnl
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN
思考:80 端口从何而来?

可以看到 bbox 的容器内监听了 80 端口,但是 bbox 内并没有启动 web 服务,那么 80 端口从何而来呢?

因为与 bbox 在同一个 Pod 内的另一个容器 nginx 监听了 80 端口,而同一个 Pod 内的所有容器是共享同一个网络名称空间的。

查看 Pod 中容器的日志

1
2
3
4
5
6
# 使用 -c 来指定容器,如只有一个容器 -c 可省略
~]# kubectl logs pod-2 -n default -c nginx
127.0.0.1 - - [31/Oct/2019:08:01:42 +0000] "GET / HTTP/1.1" 200 612 "-" "Wget" "-"
127.0.0.1 - - [31/Oct/2019:08:01:54 +0000] "GET / HTTP/1.1" 200 612 "-" "Wget" "-"
127.0.0.1 - - [31/Oct/2019:08:02:00 +0000] "GET / HTTP/1.1" 200 612 "-" "Wget" "-"
127.0.0.1 - - [31/Oct/2019:08:02:41 +0000] "GET / HTTP/1.1" 200 612 "-" "Wget" "-"

五、对外暴露 Pod 端口

在 k8s 中,如果需要将端口暴露给集群外的客户端访问,有三种方式:

  1. hostNetwork:共享所在物理机的网络名称空间;
  2. NodePort:每个物理节点中的 iptables/IPVS 都有规则;
  3. hostPort(DNAT)

NodePort

先创建一个 NodePort 类型的 Service,名字和后端 Pod 一致:

1
kubectl create service nodeport myapp --tcp=80:80

会发现在这个 svc 的端口中,映射了一个 31114 的随机端口。

1
2
3
4
~]# kubectl get svc                                  
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 111m
myapp NodePort 10.106.157.5 <none> 80:31114/TCP 2s

这是物理机上的端口,可以直接通过任意一台物理机的 IP 加 31114 端口进行访问,因为创建 NodePort 类型的 svc 后,每个节点上的 kube-proxy 程序会在所有物理机节点中的 iptables/IPVS 中添加相应的规则。

hostNetwork

Pod 创建后会被调度到某个物理机,下面这种方法可以让 Pod 共享所在物理机的网络名称空间:

1
2
3
4
5
6
7
8
9
10
11
~]# vim /root/pod-host.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-host
namespace: default
spec:
containers:
- image: nginx:latest
name: nginx
hostNetwork: ture # 开启共享物理机的网络名称空间

而后创建 Pod:

1
~]# kubectl apply -f /root/pod-host.yaml 

查看此 Pod 使用的地址,已经是宿主机的 IP 地址了:

1
2
~]# kubectl get pods -o wide
pod-host 1/1 Running 0 16s 192.168.50.11 node1.monster.com

但是这种方法并不常用,因为如果很多 Pod 都使用 80 端口的话,当他们被调度到同一台物理机时就会发生冲突了,而下面的方法可以改善这个问题。

DNAT 容器端口

把容器的端口添加一个别名,使其能被引用,而后在物理机端口中引用它,实现 DNAT。

即:hostPort -> DNAT -> containerPort

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
~]# vim /root/pod-dnat.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-dnat
namespace: default
spec:
containers:
- image: nginx:latest
name: nginx
ports:
- protocol: TCP
containerPort: 80 # 容器要使用的端口
name: http # 给上面的端口一个别名
hostPort: 8080 # 在物理机中开放端口 8080,并 DNAT 到 80

而后创建 Pod:

1
~]# kubectl apply -f /root/pod-dnat.yaml 

查看刚创建的 Pod 的地址,使用的是 Pod 网络中的 IP。Pod 被调度到 node2 节点,于是访问 node2 节点的 IP:8080 就可以访问了。

1
2
~]# kubectl get pods -o wide
pod-dnat 1/1 Running 0 11s 10.244.2.5 node2.monster.com