K8S的概念:
- Node(节点),节点机器,可以是物理机或虚拟机,比如ECS,运行了各种进程和docker等。
- Pod(容器组),应用的执行单元,创建和部署的单元;包含一个或多个容器,存储和网络资源,以及配置项。
- Controller(应用),管理Pods、复制和自愈,例如Node失败时自动迁移Pods到其他节点。
- Deployment(无状态),无状态Controller(应用)。
- Container(容器),一个Pod可以跑N>=1个Container,这些Container可以共享一些东西。
K8S命令
在macOS下可以直接用brew安装:
brew install kubectl
常用的K8S命令:
kubectl cluster-info
kubectl get cm --all-namespaces
kubectl get cm -n kube-system
kubectl get cm/srs3-config
kubectl get cm/cluster-info -n kube-public -o yaml
kubectl get cm/cluster-info -n kube-public -o json
kubectl get deploy
kubectl get deploy/nginx-deployment -o yaml
kubectl get deploy/nginx-deployment -o json
kubectl get rs
kubectl get rs -w
kubectl get pods
kubectl get pods --show-labels
kubectl edit deployment.v1.apps/nginx-deployment
kubectl api-resources # 可以查询到缩写。
kubectl get services
kubectl get svc/nginx-service -o yaml
kubectl exec srs-deployment-f4cd6b6cc-c74bm -c nginx env
kubectl get pods -o jsonpath="{.items[*].spec.containers[*].name}"
kubectl delete po/srs-origin-deployment-577cf4c7b7-zp7bs --force --grace-period=0
kubectl get po --show-labels
kubectl label deploy/srs-origin-deployment app=srs --overwrite
kubectl label po/srs-origin-deployment-6b4fcf6674-qphdz app=srs --overwrite
kubectl exec srs-edge-deploy-58d9999b7c-f4rtr -- ./objs/srs -v
kubectl set image deploy/srs-edge-deploy srs=ossrs/srs:v4.0.6
kubectl set image deploy/srs-edge-deploy srs=ossrs/srs:v4.0.6 --record
kubectl rollout history deploy/srs-edge-deploy
kubectl get rs
kubectl get rs -w # 可以Watch变化。
kubectl scale --replicas=3 deploy/srs-edge-deploy
kubectl logs srs-edge-deploy-d4cfc9d8b-5wnw7
kubectl describe po/srs-edge-deploy-d4cfc9d8b-85nkf |grep -A 3 Events
kubectl get nodes
kubectl describe nodes/cn-beijing.172.17.232.153
kubectl config get-contexts
切换集群
查看集群:
kubectl config get-contexts
设置默认集群:
kubectl config use-context kubernetes-admin-xxx
ConfigMap保存证书
ConfigMap可以配置多个文件,比如证书的私钥(*.key
)和公钥(*.pem
),后台添加配置,或者用yaml:
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: https-config
data:
https.key: |-
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA8mEyACKNJcxAfPWvAYr0uy/wVtd16LPEpy7w1l9WD9+SBsOj
n0QZSc2+y/mEpwXA65irqUply42NabB6rgfsfbcLra8SA5CbyHtrIP4fT6jnBA85
Jzmznp/q/1n4nlfaqawFyOB2IzPjPiwzexN/gv2wWC+5wSQ9yq2k
-----END RSA PRIVATE KEY-----
https.pem: |-
-----BEGIN CERTIFICATE-----
MIIFfjCCBGagAwIBAgIQD8reYBHHOc99faMYlJ0lazANBgkqhkiG9w0BAQsFADBu
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMS0wKwYDVQQDEyRFbmNyeXB0aW9uIEV2ZXJ5d2hlcmUg
-----END CERTIFICATE-----
EOF
挂到容器作为配置文件:
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: srs
labels:
app: srs
spec:
selector:
matchLabels:
app: srs
template:
metadata:
labels:
app: srs
spec:
volumes:
- name: ssl-volume
configMap:
name: https-config
containers:
- name: srs
image: registry.cn-hangzhou.aliyuncs.com/ossrs/srs:3
imagePullPolicy: IfNotPresent
volumeMounts:
- name: ssl-volume
mountPath: /usr/local/srs/etc
EOF
可以查看etc目录,就有了证书文件:
$ kubectl get pods -l app=srs
NAME READY STATUS RESTARTS AGE
srs-77b446fbfc-ftz7c 1/1 Running 0 4m50s
$ kubectl exec srs-77b446fbfc-ftz7c -- ls -lh etc
lrwxrwxrwx 1 root root 16 Feb 1 07:06 https.key -> ..data/https.key
lrwxrwxrwx 1 root root 16 Feb 1 07:06 https.pem -> ..data/https.pem
Volume
Volumes,docker有这个概念但比较简单且无生命周期管理,k8s提供各种复杂的volume,而且volume是随pod的生命周期(大于container)。
emptyDir,临时的空目录,在pod删除时会情况,container crash后目录数据还在,可用于crash后的恢复。
比如SRS写到nginx目录后,nginx可以分发切片文件。
- nginx默认目录是:/usr/share/nginx/html
- SRS的默认目录是:/usr/local/srs/objs/nginx/html
起一个Pod运行这两个容器,共享这个目录,这样就可以实现SRS写Nginx读了。
Container Args
使用command和args,command是命令,args是它的参数,一般需要一起设置。可以command指定为/bin/sh,用shell执行脚本,参数就是:
command: ["/bin/sh"]
args: ["-c", "cp -R ./objs/nginx/html/* /tmp/html/; sleep infinity"]
也可以在里面写for循环:
command: ["/bin/sh"]
args: ["-c", "while true; do echo hello; sleep 10;done"]
还可以写成数组的方式,字符串用长字符串换行:
command: ["/bin/sh"]
args:
- "-c"
- >
while true; do
ffmpeg -re -i ./doc/source.200kbps.768x320.flv \
-c copy -f flv rtmp://srs/live/livestream
done
可以用脚本做初始化的事情。也可以只用args,会从args中解析出command:
args:
- /bin/sh
- -c
- echo hello
Resource
管理容器使用的计算资源,Resource。
可以创建Pod时指定需要的CPU和内存,这样调度时知道调度到哪个节点;还可以指定资源的限额。资源限额定义,Quota。
资源的要求和限制,指定在:
- spec.containers[].resources.limits.cpu
- spec.containers[].resources.limits.memory
- spec.containers[].resources.requests.cpu
- spec.containers[].resources.requests.memory
CPU,比如
cpu: 0.1
就是100m
,m就是千分之,millicpu,millicores。
Memory,比如memory:100Mi
就是100MiB
,还有Ki,Gi,Ti,Pi。
limits是最多使用的限制;requests是需要使用的,也就是要求的资源。
0<=Request<=Limit<=Infinity (如果Limit为0表示不对资源进行限制,这时可以小于Request)
关于调度:
- 调度时会根据request的资源,保障总的request资源小于Node容量。所以request的资源,是比实际运行的要小的。
- 或者可以认为,低峰期时空闲时CPU是1%,高峰时可能request到30%,limit到50%。调度时就根据各个Pod的30%作为上限。
关于limit:
- K8S启动容器时,会把request cpu传递到容器runtime中,docker run的-c参数:
- 默认是1024,至少是2。如果某个容器是空闲的,其他容器可以使用剩下的全部的CPU。某个容器能使用的最多CPU是变化的。
- 例如如果一个是1024,两个是512,如果容器内的CPU都跑到100%了,那么实际上第一个是50%,剩下两个是25%。
- 如果再加第四个容器也是1024,那么第一个是33%,第二和三是16.5%,第四是33%。
- 虽然每个容器的CPU份额是小于100%的,但在多核的机器上可能让多个CPU跑100%。
- 比如有3个CPU,第一个容器是512,另外是1024,那么可能CPU0是100%跑第一个容器,CPU1和CPU2也是100%跑第二个容器。从系统总体看CPU是300%,100%给了第一个容器,200%给了第二个容器。
- 而limit cpu,则会传递到docker的cpu-quota参数。
- 而limit memory,则会传递到docker的memory参数:
- 如果容器超过内存限制limits.memory,很可能重启。内存和磁盘是不可压缩资源。
- 如果容器超过要求的内存requests.memory,在节点超过能力时会被迁移。
- 容器可能会被允许超过CPU的限制,但不会因为CPU过高而被KILL。CPU是可压缩资源。
- 如果容器因为资源限制被终止:
- 查看容器的事件:kubectl describe po/srs-edge-deploy-d4cfc9d8b-85nkf |grep -A 3 Events
- 查看节点调度的资源情况:kubectl describe nodes/cn-beijing.172.17.232.153
改进:
关于QoS:
- Guranteed,最高保障,request和limit相等,只指定了limit(就默认request等于limit)。
*Burstable,Strong wish,强烈要求,request和limit有设置,但不相等。 - Best-Effort,最好能支持,完全不设置。
OOM时KILL的顺序:最先KILL Best-Effort,然后是Burstable,最后是Guranteed。
Config Reload
ConfigMap修改后,文件会变。
注意:fsnotify signal可能因为符号链接收不到。
Linux是使用inotify机制,用inotify_init返回fd,watch后如果有变化可以read出来。
可以侦听所有事件:IN_ALL_EVENTS,但主要是IN_MODIFY和IN_CREATE。
返回的内容转换成结构体:inotify_event
。
YAML格式
-表示列表,比如ports可以指定多个,所以就以-开头。
管道命令行apply:
cat <<EOF | kubectl apply -f -
apiVersion: v1
EOF
可以把多个yaml写在一起,用三个横杠分割,比如stateful:
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
---
apiVersion: apps/v1
kind: StatefulSet
EOF
配置镜像拉取,如果存在就不拉取images:
spec.containers[*].imagePullPolicy: IfNotPresent
如果总是拉取,则设置为Always
。
Service
Service就是如何让Pods提供服务,或微服务,涉及服务发现和负载均衡。
一般服务发现使用的是label selector,有时候也需要无selector的Service(必须后端是另外一个K8S或非Pod)。标签(label)就是分组的一种方式了,或者一种ID。
定义服务时,需要指定selector比如app=nginx,指定服务端口port,以及后端端口targetPort(默认等于port)。
注意targetPort可以是字符串,会解析成对应的端口,这样后端服务会比较灵活。
下面是一个服务的描述。
cat <<EOF | kubectl apply -f -
kind: Service
apiVersion: v1
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
EOF
查询该服务:
$ kubectl get svc/nginx-service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-service ClusterIP 172.21.7.190 <none> 80/TCP 3m13s
服务是由kube-proxy分配的VIP并进行代理,没有采用DNS因为有缓存等问题:
kube-proxy有几种不同的代理方式:
- user space proxy mode,用户空间代理模式,proxy为每个service侦听一个端口,通过iptables将服务的请求转发到这个端口然后转给pod。
- iptables proxy mode,iptables代理,proxy为每个service新增一条转发规则,负载高应该是内核netfilter转发,但出错后不会重试。
- IPVS proxy mode,1.11后支持了IPVS代理,使用的是内核的netlink,比iptables性能更高吞吐率也更高。
如果杀掉nginx的pod,如果只有一个pod会服务不可用,但过一会儿K8S会重新启动pod,服务就可以了。
Service Type
ServiceTypes服务类型,一般ClusterIP就是内部服务,还有其他的:
- ClusterIP,默认就是这种,集群内部可以访问。分配的是Service网段IP(172.21.*),有内部端点无外部端点。
- NodePort,绑定到Node上,也就是Node(ECS)网段(172.17.*),会自动创建ClusterIP服务。外部可以通过Node的IP访问服务。
- LoadBalancer,通过云服务的负载均衡实现,会自动创建NodePort和ClusterIP。外部访问的是负载均衡。
- ExternalName,绑定到CNAME,比如foo.example.com,不会创建代理,coredns1.7及以上才支持。
默认是ClusterIP,看到内网的IP(ClusterIP),但无外部IP(ExternalIP),ClusterIP 172.21.89.226 <none> 80/TCP,可访问性如下:
- nginx的realserver是在172.17.232.153
- nginx的POD的IP,可在跳板机访问:curl http://172.20.0.24
- nginx-service的IP是172.21.89.226,无法在跳板机访问,但可以在node上访问:curl http://172.21.89.226
LoadBalancer负载均衡,一般阿里云标准用法是EIP绑定到SLB上,通过SLB对外提供服务。SLB创建时选内部SLB,交换机选k8s-node使用的,比如IP是172.17.232.159。然后再买EIP绑定到SLB。不用创建SLB侦听,k8s会自动做这事,创建后是这样:
$ kubectl get svc/nginx-service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-service LoadBalancer 172.21.113.176 172.17.232.159 80:30589/TCP 6s
可以发现,类型是LoadBalancer,设置了ExternalIP。
Service Discovery
服务发现可以用ENV环境变量,或者DNS。
当注册Service后,K8S会在POD启动时,增加服务对应的环境变量,那么POD就可以用这个环境变量来发现服务了。
CoreDNS
组件开启后,创建了Service就会创建对应ServiceName的DNS记录,比如nginx-service。
$ kubectl get service/nginx-service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-service ClusterIP 172.21.7.190 <none> 80/TCP 16m
$ kubectl exec srs3-demo-deploy-55df684cbb-5dzsh -- ping nginx-service
PING nginx-service.default.svc.cluster.local (172.21.7.190) 56(84) bytes of data.
64 bytes from nginx-service.default.svc.cluster.local (172.21.7.190): icmp_seq=1 ttl=64 time=0.033 ms
StatefulSets
时,需要给每个Pod分配可达的地址,这就是HeadlessServices。通过配置:ClusterIP: None 来指定的。
声明式更新
Declarative updates,声明式更新:
However, a Deployment is a higher-level concept that manages
ReplicaSets and provides declarative updates to Pods along with
a lot of other useful features.
这文章说declarative updates:
-
kubectl apply
一般是declarative。参考#1和 #2 -
kubectl run
会转成declaratively Deployment:kubectl translates your imperative command into a declarative Kubernetes Deployment object. A Deployment is a higher-level API that allows rolling updates (see below). - 意味着保持期望状态和实际状态一致:The Kubernetes API is fundamentally declarative, which means that the controllers always work to reconcile the observed state with the desired state. Therefore, if we delete a Pod, the ReplicaSet controller will create a new one to replace it, to maintain the desired replica count.
- 配置你想要的,K8S知道怎么实现:However, the power of Kubernetes is in its declarative API and controllers. You can just tell Kubernetes what you want, and it will know what to do.
- apply是
幂等
操作,可以执行多次:The kubectl apply command is idempotent. We can reuse it after modifying the manifests.
官方也有几个文章说Imperative命令式和Declarative声明式:
- 命令式,Managing Kubernetes Objects Using Imperative Commands
- 声明式,Declarative Management of Kubernetes Objects Using Configuration Files
- Kubernetes Object Management,对比了命令式和声明式的差异。
Aliyun ACK
ACK托管集群,需要的基本资源:
- 至少
1
个worker,2CPU
,2GB
。包括,Replicas:10个,内存:850m,CPU:1030Mi。 - 虽然coredns是两个replicas,但是也可以运行在一个worker上面。
- 至少
3
个IP,1个是kubectl访问集群用,1个是SLB对外提供服务用,1个是绑定到worker访问外网用(比如下载更新docker镜像)。