Fork me on GitHub
Fork me on GitHub

kubernetes应用快速入门

kubectl简介

API Server是Kubernetes集群的网关,任何人只能通过API Server接口与集群进行交互。而kubectl是Kubernetes的终端客户端,此前博客里讲解的使用kubeadm初始化Kubernetes集群时,kubeadm init自动生成的/etc/kubernetes/admin.conf是kubectl接入k8s集群时使用的kubeconfig配置文件,它内建了用于访问Kubernetes集群的最高管理权限的用户账号及相关的认证凭据。
kubectl提供了基于命令行访问Kubernetes API的简洁方式,能够满足对Kubernetes的绝大部分的操作需求。


taint:污点。taint是跟高级调度相关的,给节点增加污点以后,能容忍这污点的pod就可以调度到这个节点上,否则就不能调度上来。其实master上就有很多的污点,这就是为什么我们创建pod不会调度到master节点上。因为默认创建的所有pod都无法容忍master的污点。这样确保了master只用于运行apiserver等组件。我们在定义pod时可以定义容忍度(tolerance)。
查看master节点上的污点:

1
2
3
4
5
6
[root@spark32 ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
spark17 Ready <none> 111d v1.14.1
spark32 Ready master 111d v1.14.1
ubuntu31 Ready <none> 111d v1.14.1
[root@spark32 ~]# kubectl describe node spark32


查看node上有没有污点:

查看集群信息和集群组件状态信息:

kubectl增删改查

示例1:创建一个Nginx的Pod


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@spark32 ~]# kubectl run nginx-deploy --image=nginx:1.14-alpine --port=80 --replicas=1 --dry-run=true
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
deployment.apps/nginx-deploy created (dry run)
You have mail in /var/spool/mail/root
[root@spark32 ~]# kubectl run nginx-deploy --image=nginx:1.14-alpine --port=80 --replicas=1
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
deployment.apps/nginx-deploy created
[root@spark32 ~]# kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deploy 1/1 1 1 8s
[root@spark32 ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-deploy-55d8d67cf-bh8z5 1/1 Running 0 26s
[root@spark32 ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deploy-55d8d67cf-bh8z5 1/1 Running 0 35s 10.244.2.5 ubuntu31 <none> <none>

【说明】:

  • –dry-run=true:干跑模式,并没有真正运行
  • –port=80:不指定这个也会暴露容器中的相应端口
  • 默认创建的是其实是Deployment,如果想创建一个Pod,而不是想要创建由Deployment控制器控制的pod,可以在创建命令上加一个–restart=…
    1
    2
    # kubectl run nginx --image=nginx:1.14-alpine --port=80 --restart=Never
    pod/nginx created


这是Pod的地址,只能在k8s集群内使用。Pod的客户端一般有两类:

  • 其他Pod
  • 集群外部客户端:怎么访问?

Pod地址可能会变化的,假设我们误删除了pod:

[root@spark32 ~]# kubectl get pods
NAME                           READY   STATUS    RESTARTS   AGE
nginx-deploy-55d8d67cf-bh8z5   1/1     Running   0          9m10s
[root@spark32 ~]# kubectl delete pods nginx-deploy-55d8d67cf-bh8z5
pod "nginx-deploy-55d8d67cf-bh8z5" deleted
[root@spark32 ~]# kubectl get pods -o wide
NAME                           READY   STATUS    RESTARTS   AGE   IP           NODE      NOMINATED NODE   READINESS GATES
nginx-deploy-55d8d67cf-qrd2s   1/1     Running   0          11s   10.244.1.4   spark17   <none>           <none>

删除了pod,控制器Deployment发现副本少了一个,会自动创建一个。但是这个新Pod的IP地址已经变了,跑到另外一个node主机上去了。
在上一篇博文中,提到了需要在Pod前创建一个service,让service关联到Pod上。这样用户在访问时只需要访问service的地址或名称,只要service不被删除,后端的Pod因为各种原因被终止掉或删除掉后重新创建后,哪怕IP地址变了也没什么关系。

示例2:创建service

1
2
3
4
5
6
7
8
[root@spark32 ~]# kubectl expose --help
...
--type='': Type for this service: ClusterIP, NodePort, LoadBalancer, or ExternalName. Default is 'ClusterIP'.
Usage:
kubectl expose (-f FILENAME | TYPE NAME) [--port=port] [--protocol=TCP|UDP|SCTP] [--target-port=number-or-name]
[--name=name] [--external-ip=external-ip-of-service] [--type=type] [options]
...

【说明】:

  • –type=’’:指定service类型。service类型有4种,默认是ClusterIP。
1
2
3
4
5
6
7
[root@spark32 ~]# kubectl expose deployment nginx-deploy --name=nginx --port=80 --target-port=80 --protocol=TCP
service/nginx exposed
You have mail in /var/spool/mail/root
[root@spark32 ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 111d
nginx ClusterIP 10.109.152.170 <none> 80/TCP 10s


【说明】:

  • svc是service的简称

通过service访问服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[root@spark32 ~]# curl 10.109.152.170
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>

外部节点,是无法通过service这个地址来访问这个服务的。
更多的时候,这个service地址是被Pod客户端来访问的,而且被访问时是完全可以基于service名称来访问。只不过需要Pod客户端可以解析名称,这就需要用到CoreDNS服务。之前的博文《kubeadm安装kubernetes 1.14.0》里安装集群时已经把CoreDNS这个附件安装好了。

创建个Pod,作为客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
[root@spark32 ~]# kubectl run client --image=busybox --replicas=1 -it --restart=Never
If you don't see a command prompt, try pressing enter.
/ # cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
/ # nslookup nginx.default.svc.cluster.local
Server: 10.96.0.10
Address: 10.96.0.10:53
*** Can't find nginx.default.svc.cluster.local: No answer
/ # ping nginx
PING nginx (10.109.152.170): 56 data bytes
^C
--- nginx ping statistics ---
5 packets transmitted, 0 packets received, 100% packet loss
/ # wget -O - -q nginx
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
/ # curl nginx
sh: curl: not found

【说明】:

  • Pod中的默认DNS就是CoreDNS service的地址。
  • svc.cluster.local:这是特殊的域名,表示是你的Kubernetes集群的本地Pod资源,特定后缀。
  • default:Pod所属于的名称空间的名称。
  • ping nginx,解析到的IP地址10.109.152.170是nginx这个service的IP地址,至于为什么ping不通,是因为service的地址仅仅是存在于iptables规则或ipvs规则中。
  • 在Pod中可以使用service名称来访问,因为有DNS可以解析service的名称

我们可以在节点上解析这个service名称:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@spark32 ~]# dig -t A nginx.default.svc.cluster.local @10.96.0.10
; <<>> DiG 9.9.4-RedHat-9.9.4-29.el7 <<>> -t A nginx.default.svc.cluster.local @10.96.0.10
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 53679
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;nginx.default.svc.cluster.local. IN A
;; ANSWER SECTION:
nginx.default.svc.cluster.local. 5 IN A 10.109.152.170
;; Query time: 0 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Fri Aug 02 13:37:22 CST 2019
;; MSG SIZE rcvd: 107

【注意】:在节点级解析service时需要输入service名称的全程,而不能仅仅使用service的简称来解析,因为节点级别的搜索域和Pod内的搜索域是不一样的。

这个service调度给我们之前创建的Pod了,service给有生命周期的Pod提供了固定访问的端点。我们现在人为把nginx Pod搞挂。

接着在Pod客户端再次访问下service:

当一个service生成时,会在每个节点上生成相应的iptables规则或ipvs规则,当客户端访问这个service地址和相应的端口时,就会调度到这个service利用标签选择器关联到的Pod了。
在上面的示例中,会把所有访问10.109.152.170:80这个地址的都调度至这个service用label-selector(标签选择器)关联到的各Pod后端。

当我们修改这个service或者删除这个service再重新建个service,会立刻反应到CoreDNS的解析记录中去。

1
2
3
4
5
6
7
8
[root@spark32 ~]# kubectl delete svc nginx
service "nginx" deleted
[root@spark32 ~]# kubectl expose deployment nginx-deploy --name=nginx
service/nginx exposed
[root@spark32 ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 111d
nginx ClusterIP 10.107.191.247 <none> 80/TCP 44s

service对应的IP已经改变了,我们在Pod客户端里再测试下访问这个service,发现仍然可以根据service名称访问:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/ # wget -O - -q nginx
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>

示例3:副本

1
2
3
4
5
6
7
[root@spark32 ~]# kubectl run myapp --image=ikubernetes/myapp:v1 --replicas=2
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
deployment.apps/myapp created
[root@spark32 ~]# kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
myapp 2/2 2 2 15s
nginx-deploy 1/1 1 1 171m


通过Pod IP访问下服务:

1
2
[root@spark32 ~]# curl 10.244.2.7
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>

为这个deploy创建一个service:

1
2
3
4
5
6
7
[root@spark32 ~]# kubectl expose deploy myapp --name=myapp --port=80
service/myapp exposed
[root@spark32 ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 111d
myapp ClusterIP 10.109.204.135 <none> 80/TCP 4s
nginx ClusterIP 10.107.191.247 <none> 80/TCP 17m

在Pod中访问这个服务:

1
2
3
4
5
6
7
8
9
10
11
12
/ # wget -O - -q 10.244.2.7
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
/ # wget -O - -q myapp
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
/ # while true; do wget -O - -q myapp/hostname.html; sleep 1; done
myapp-5bc569c47d-mkdqg
myapp-5bc569c47d-qht8j
myapp-5bc569c47d-qht8j
myapp-5bc569c47d-qht8j
myapp-5bc569c47d-mkdqg
myapp-5bc569c47d-mkdqg
...

示例4:扩展和缩减副本

接着上面的示例,我们扩展myapp这个deployment中的副本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[root@spark32 ~]# kubectl scale --replicas=5 deploy myapp
deployment.extensions/myapp scaled
[root@spark32 ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
client 1/1 Running 0 63m
myapp-5bc569c47d-9sbwq 1/1 Running 0 5s
myapp-5bc569c47d-cmgsl 1/1 Running 0 5s
myapp-5bc569c47d-mkdqg 1/1 Running 0 17m
myapp-5bc569c47d-qht8j 1/1 Running 0 17m
myapp-5bc569c47d-wv7sw 1/1 Running 0 5s
nginx-deploy-55d8d67cf-wf28j 1/1 Running 0 40m
[root@spark32 ~]# kubectl scale --replicas=3 deploy myapp
deployment.extensions/myapp scaled
[root@spark32 ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
client 1/1 Running 0 64m
myapp-5bc569c47d-9sbwq 0/1 Terminating 0 26s
myapp-5bc569c47d-cmgsl 1/1 Running 0 26s
myapp-5bc569c47d-mkdqg 1/1 Running 0 17m
myapp-5bc569c47d-qht8j 1/1 Running 0 17m
myapp-5bc569c47d-wv7sw 0/1 Terminating 0 26s
nginx-deploy-55d8d67cf-wf28j 1/1 Running 0 40m
[root@spark32 ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
client 1/1 Running 0 64m
myapp-5bc569c47d-cmgsl 1/1 Running 0 30s
myapp-5bc569c47d-mkdqg 1/1 Running 0 17m
myapp-5bc569c47d-qht8j 1/1 Running 0 17m
nginx-deploy-55d8d67cf-wf28j 1/1 Running 0 40m

【说明】:

  • Terminating:表示正在终止Pod。

示例5:滚动更新程序

除了以上的动态伸缩,还可以做滚动更新。比如将myapp从v1滚动更新到v2。比如现在有3个Pod,它会先替换1个,在替换1个,在替换1个。

1
2
3
4
5
6
[root@spark32 ~]# kubectl set image --help
...
Usage:
kubectl set image (-f FILENAME | TYPE NAME) CONTAINER_NAME_1=CONTAINER_IMAGE_1 ... CONTAINER_NAME_N=CONTAINER_IMAGE_N
[options]
...

查看当前Pod所使用的image镜像版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@spark32 ~]# kubectl set image deploy myapp myapp=ikubernetes/myapp:v2
deployment.extensions/myapp image updated
[root@spark32 ~]# kubectl rollout status deploy myapp
Waiting for deployment "myapp" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "myapp" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "myapp" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "myapp" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "myapp" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "myapp" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "myapp" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "myapp" rollout to finish: 1 old replicas are pending termination...
deployment "myapp" successfully rolled out
[root@spark32 ~]#

在Pod中访问下服务,已经全部变为v2版本了:

1
2
3
4
5
6
7
8
/ # while true; do wget -O - -q myapp; sleep 1; done
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>
...

示例6:回滚



1
2
[root@spark32 ~]# kubectl rollout undo deploy myapp
deployment.extensions/myapp rolled back

如果需要在集群外部访问myapp,需要修改service类型为NodePort。

1
2
3
4
5
6
[root@spark32 ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 111d
myapp ClusterIP 10.109.204.135 <none> 80/TCP 40m
nginx ClusterIP 10.107.191.247 <none> 80/TCP 58m
[root@spark32 ~]# kubectl edit svc myapp


【注意】:kubectl edit在编辑资源时,有些字段是不能这样修改的,后面在写yaml清单文件会介绍。

打开浏览器,访问集群任何一个Node的30020端口:

但是假如这个Node挂了,对客户端来说就不友好了,它得改地址为其他的Node地址。所以最好手工在集群外做个物理的负载均衡器。

service到底是什么?
service是在集群每个节点上生成的iptables规则或者ipvs规则。

1
[root@spark32 ~]# iptables -L -n -v -t nat