K8S 学习笔记
本文目录
1. 安装依赖
安装基本依赖及 docker 仓库
$ curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
$ yum install yum-utils device-mapper-persistent-data lvm2 wget vim net-tools ntp -y
$ yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
安装数据库、git 及 docker
yum install -y mysql git docker-ce-19.03.11
systemctl start docker
systemctl enable docker
拉取 java 项目代码
mkdir kubeblog
cd kubeblog
git init
git pull https://git.imooc.com/coding-464/kubeblog.git
# wongbingming@163.com yang1994,.
启动 mysql 前的预备工作
mkdir /opt/mysql
mkdir /opt/mysql/conf.d
mkdir /opt/mysql/data/
cat <<EOF >/opt/mysql/my.cnf
[mysqld]
user=mysql
character-set-server=utf8
default_authentication_plugin=mysql_native_password
secure_file_priv=/var/lib/mysql
expire_logs_days=7
sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
max_connections=1000
[client]
default-character-set=utf8
[mysql]
default-character-set=utf8
EOF
运行 mysql 的容器
docker run \
--name mysql57 \
-p 3306:3306 \
-v /opt/mysql/data:/var/lib/mysql \
-v /opt/mysql/log:/var/log/mysql \
-v /opt/mysql/my.cnf:/etc/mysql/my.cnf:rw \
-e MYSQL_ROOT_PASSWORD=password \
-d registry.cn-beijing.aliyuncs.com/qingfeng666/mysql:5.7 --default-authentication-plugin=mysql_native_password
使用默认的密码 password
尝试登陆并创建 blogDB 数据库
mysql -uroot -h127.0.0.1 -p
MySQL [(none)]> CREATE DATABASE `blogDB` CHARACTER SET utf8 COLLATE utf8_general_ci;
安装 java、maven 并打包项目
yum install maven java-1.8.0-openjdk-devel
cd /root/kubeblog/Final/
mvn package
运行 java 项目
java -jar target/kubeblog.jar
如一切正常
- 访问博客:http://localhost:5000
- 访问博客管理员端:http://localhost:5000/admin,用户名/密码:admin/password
https://developer.aliyun.com/mirror/centos
unshare --fork --pid --mount-proc bash
grant all privileges on *.* to 'root'@'localhost' identified by '123456';
2. K8S 架构
ip | 域名 | 角色 | 安装软件 |
---|---|---|---|
192.168.56.200 | master | 主节点 | docker kubeadm kubelet kubectl finanel |
192.168.56.201 | node1 | 从节点 | docker kubeadm kubelet kubectl finanel |
192.168.56.202 | node2 | 从节点 | docker kubeadm kubelet kubectl finanel |
对环境的要求:
- 3 台虚拟机:每台 2CPU,硬盘30G,内存2GB
- 3 台虚拟机内网互通
- 3 台虚拟机可访问外网
- 禁止 swap 分区
3. 安装 K8S
先安装源
curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
然后再安装 net-tools,如果报如下的错
repomd.xml signature could not be verified for kubernetes
可以关闭 /etc/yum.repos.d/kubernetes.repo
的验证
gpgcheck=0
repo_gpgcheck=0
同步下时间
ntpdate 0.asia.pool.ntp.org
加载 netfilter 模块,并查看一下文件是否为1
$ modprobe br_netfilter
$ cat /proc/sys/net/bridge/bridge-nf-call-iptables
1
新建 /etc/sysctl.d/k8s.conf,内容如下
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
关闭防火墙
systemctl stop firewalld
systemctl disable firewalld
setenforce 0
关闭 SELinux,编辑 /etc/selinux/config 修改如下值
SELINUX=disable
关闭 swap
swapoff -a
新建文件
cat > /etc/docker/daemon.json <<EOF
{
"exec-opts": ["native.cgroupdriver=systemd"],
"registry-mirrors": ["https://registry.cn-hangzhou.aliyuncs.com"]
}
EOF
完了后,重启 docker
# Restart Docker
systemctl daemon-reload
systemctl restart docker
systemctl enable docker
执行命令安装
yum install -y kubelet-1.19.3 kubeadm-1.19.3 kubectl-1.19.3
构建 k8s 集群,如果你的虚机 cpu 不足2 核这边就会报错,这次命令只要执行一次,以后都不需要
kubeadm init --kubernetes-version=1.19.2 \
--apiserver-advertise-address=192.168.56.200 \
--image-repository registry.aliyuncs.com/google_containers \
--service-cidr=10.1.0.0/16 \
--pod-network-cidr=10.244.0.0/16
顺便把最后的输出结果中,把如下这个记录下,后面添加 worker 节点会用到,如果没有记也没有关系,可以重新生成的
kubeadm join 192.168.56.200:6443 --token p2tz0y.1h3e0c8yriqlcpcr \
--discovery-token-ca-cert-hash sha256:8918da682f9cefd47bb240bf6ec32da3eec45c396a1125bb116111352e559e19
# 重新生成命令
kubeadm token create --print-join-command
完成后,再执行这四条命令
mkdir -p HOME/.kube
sudo cp -i /etc/kubernetes/admin.confHOME/.kube/config
sudo chown (id -u):(id -g) $HOME/.kube/config
echo "export KUBECONFIG=/etc/kubernetes/admin.conf" >> ~/.bash_profile
然后要安装 flannel 插件
git clone https://git.imooc.com/coding-464/kubeblog.git
kubectl apply -f kubeblog/docs/Chapter4/flannel.yaml
此时执行命令
[root@master ~]# kubectl get po -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-6d56c8448f-cbwg5 1/1 Running 0 8m5s
coredns-6d56c8448f-pmcvb 1/1 Running 0 8m5s
etcd-master 1/1 Running 0 8m22s
kube-apiserver-master 1/1 Running 0 8m22s
kube-controller-manager-master 0/1 Running 1 8m22s
kube-flannel-ds-b7lgp 1/1 Running 0 95s
kube-proxy-qs7wp 1/1 Running 0 8m5s
kube-scheduler-master 0/1 Running 1 8m21s
再使用 kubectl get node
可以发现已经装好了
[root@master Chapter4]# kubectl get node
NAME STATUS ROLES AGE VERSION
master Ready master 9m12s v1.19.3
设置开机自启
systemctl start kubelet.service
systemctl enable kubelet.service
4. 概念性术语
Cluster
是计算、存储和网络资源的集合,Kubernetes 利用这些资源运行各种基于容器的应用。一个 Cluster 由 Master 节点和 Node 节点两种角色组成。
Master
在 Cluster 中起到管理和控制的作用。基本上所有的 Kubernetes 控制命令都是直接发给 Master 。
它决定了应用放在哪个 Node 上运行,Master 是 Kubernetes 集群的大脑,非常地重要,因此为了避免 Master 宕机造成的巨大影响,通常会启用三台独立的机器 部署 Master 实现高可用。
在 Master 节点上起着四个关键服务
- Kubernetes API Server(kube-apiserver):提供了HTTP Rest接口的关键服务进程,是Kubernetes里所有资源的增、删、改、查等操作的唯 一入口,也是集群控制的入口进程
- Kubernetes Controller Manager ( kube-controller-manager ) : Kubernetes里所有资源对象的自动化控制中心,可以将其理解为资源对象的“大总管”
- Kubernetes Scheduler(kube-scheduler):负责资源调度(Pod调度)的进程,相当于公交公司的“调度室”。
- etcd:这是一个高可用的分布式 key-value 数据库,用于存储 Kubernetes 中所有的资源对象数据。
Node
在 Cluster 中的作用是运用容器应用。Node 由 Master 管理,Node 负责监控并汇报容器的状态,并根据 Master 的要求管理容器的生命周期。Master 本身也可以作用 Node 节点工作。
在 Node 节点上,运行的关键服务有如下三个:
- kubelet:负责Pod对应的容器的创建、启停等任务,同时与Master密切协作,实现集群管理的基本功能
- kube-proxy:实现Kubernetes Service的通信与负载均衡机制的重要组件
- Docker Engine(docker):Docker引擎,负责本机的容器创建和管理工作
这三个同样也可安装运行于 Master,然后就可以将 Master 也变成 Node 节点 join 到 集群中。
# 把 Master 当作 Node 使用
kubectl taint node master node-role.kubernetes.io/master-
# 恢复 Master Only 状态
kubectl taint node master node-role.kubernetes.io/master="":NoSchedule
另外,在Master 和 Node 上还需要安装网络插件,实现 Pod 间的通信,通常使用的是 flannel ,对应的服务是 flanneld,主要负责数据包的转发处理,详情可参考:k8s的Flannel网络,由于这只是一个插件,如果不想用它,也可以用别的,因此是非必须的。
一旦Node 被纳入集群管理范围,kubelet进程就会定时向Master汇报自身的情报,例 如操作系统、Docker版本、机器的CPU和内存情况,以及当前有哪些Pod 在运行等,这样Master就可以获知每个Node的资源使用情况,并实现高 效均衡的资源调度策略。
而某个Node在超过指定时间不上报信息时,会 被Master判定为“失联”,Node的状态被标记为不可用(Not Ready),随 后Master会触发“工作负载大转移”的自动流程。
Pod
Kubernetes 的最小的工作和管理单元。每个 Pod 里包含一个或者多个容器,Pod 中的容器会作为一个整体被 Master 调度到一个 Node 上运行。一个 Pod 上的多个容器,共享 ip 和 port,使用同一个 namespace。
Pod 还可以细分为两种类型:
- 静态Pod:其比较特殊,它并没被存放在Kubernetes的etcd存储里,而是被存放在某个具体的Node上的一个具体文件中,并且只在此Node上启动、运行
- 普通的Pod:一旦被要求创建,就会被存放入 etcd 存储中,随后被 Master 调度到某个具体的 node 上由该 node 上的 kubelet 创建,然后实例化成一组相关的 Docker 容器并启动。当该 Pod 中的某个容器停止时,Kubernetes会自动检测到这个问题并且重新启动这个Pod(重启Pod里的所有容器),如果Pod所在的Node宕机, 就会将这个Node上的所有Pod重新调度到其他节点上。
Container
也即容器,是 Pod 的基本组成单位,一个 Pod 必须包含至少一个 Container。
多个联系非常紧密且需要直接共享资源(比如磁盘、ip等)的容器可以放在一个 Pod 中运行。
每一个 Pod 里,除了业务容器外,还都有一个特殊的根容器,我们称之为 Pause 容器,它属于 Kubernetes平台的一部分。
为什么Kubernetes会设计Pod这样特殊的组成结构?
- 原因之一:在一组容器作为一个单元的情况下,我们难以简单地对 “整体”进行判断及有效地行动。比如,一个容器死亡了,此时算是整体死亡么?是N/M的死亡率么?引入业务无关并且不易死亡的Pause容器作为Pod的根容器,以它的状态代表整个容器组的状态,就简单、巧妙地解决了这个难题
- 原因之二:Pod里的多个业务容器共享Pause容器的IP,共享Pause容器挂接的Volume,这样既简化了密切关联的业务容器之间的通信问题, 也很好地解决了它们之间的文件共享问题
5. 认识 Pod
5.1 如何创建资源对象
Kubernetes 和 Ansible 的设计上非常相似,只不过
- Ansible 是用来编排任务的
- 而 Kubernetes 则用来编排容器应用的
那怎么编排呢?Kubernetes 的任务都是使用的 Yaml 文件进行的,因此在学习 Kubernetes 之前,有必要大致先了解一下 Yaml 文件编写规范。
那么如何将 容器应用的各个属性及所需的参数,写到 Yaml 文件中呢?
Kubernetes 有一套自己的协议,这也是我们需要花长时间去学习并适应的。
有了 Kubernetes 可以解析的 Yaml 文件后 ,就可以通过 kubectl create
或者 kubectl apply
这两命令去创建并运行它。
那么这两条命令有什么区别呢?区别在于修改pod的配置时,操作的姿势:
kubectl create
:可以使用kubectl edit
/kubectl set image
或者直接修改 nginx.yaml 先修改,再使用kubectl replace -f nginx.yaml
去替换(是使用新的 YAML 文件中的 API 对象,替换原有的 API 对象)kubectl apply
:只能通过直接编辑nginx.yaml
修改配置,再通过kubectl apply -f nginx.yaml
应用(执行了一个对原有 API 对象的 PATCH 操作。)
像上面一样,先定义好 yaml 配置,有几个好处
- yaml 配置文件提供了创建资源的模板,能够复用实现重复部署
- 可以像管理代码一样,管理项目的部署
- 适合正式的、跨环境的、规模化的部署
好处虽然多多,却也有弊端,那就是上手困难,要先熟悉 Kubernetes 的模板协议,才知道如何编写。
除了使用yaml 配置文件的方法外,也可以使用命令行的方式去创建资源对象,这种方法上手快,适合那种一次性的临时测试使用
kubectl run <pod_name> --image <image>
5.2 创建一个 Pod
首先以 Pod 为例进行演示,先准备好 nginx.yaml 文件,内容如下
apiVersion: v1
kind: Pod
metadata:
name: my-nginx
labels:
name: my-nginx
spec:
containers:
- name: my-nginx
image: registry.cn-beijing.aliyuncs.com/qingfeng666/nginx:latest
resources:
limits:
memory: "128Mi"
cpu: "500m"
ports:
- containerPort: 80
然后执行下面两条命令中的任意一条即可
$ kubectl create -f nginx.yaml
$ kubectl apply -f nginx.yaml
创建完成后,就可以通过 kubectl get pod
来查询到你刚刚所创建的 Pod
5.3 创建 init 容器
在一个 Pod 里,可以分为三种容器:
- Pause 容器:上面已经讲过
- init 容器:用于应用环境的初始化,仅当 init 容器运行完成后,才会开始运行普通容器
- 普通容器:除上面两种容器外的,都是普通容器,里面运行着跟业务相关的程序。
一旦 init 容器启动失败,Pod 会不断的重启该 Pod,直到 init 容器成功为止。但如果 Pod 的 restartPolicy 值设成了 Never,那就不会重启了。
以下是一个带有 init 容器的 Pod yaml 文件
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: myapp-container
image: busybox:1.28
command: ['sh', '-c', 'date && sleep 3600']
initContainers:
- name: init-container
image: busybox:1.28
command: ['sh', '-c', "date && sleep 10"]
6. 认识 Controller
创建 Deployment 对象
创建双副本的 nginx
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 2
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: registry.cn-beijing.aliyuncs.com/qingfeng666/nginx:latest
ports:
- containerPort: 80
执行后就会创建两副本的 nginx pod
创建 Service 对象
每个 Pod 在创建的时候,都会分配有一个 IP 地址,当这个 Pod 因为故障或者其他原因重启时,这个 IP 地址就会重新分配,IP 地址会发生变化。
对于一个应用来说,如果 IP 地址一直发生变化,客户端的访问就会出现问题。
对此,Kubernetes 给出的解决方案是 Service 。
使用 Service ,可以解决两个问题:
- 应用的 IP 固定问题
- 应用的负载均衡问题
Service 可以将一组 Pod 上的应用程序公开为网络服务,实现负载均衡。
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
run: my-nginx
spec:
ports:
- port: 80
protocol: TCP
selector:
run: my-nginx
通过 kubectl get svc
就可以看到 Cluster-IP,通过这个IP去访问的话,会分发请求给后端的两个 pod。
创建 Ingress 对象
ingress 对象的作用是管理 HTTP 请求的路由转发规则
# kubectl apply -f minimal-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: minimal-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- http:
paths:
- path: /testpath
pathType: Prefix
backend:
service:
name: test
port:
number: 80
每个 HTTP 规则都包含如下信息
- 可选的host,在上面的示例中没有指定 host,因此 该规则适用于指定 ip地址进行访问的请求,如果指定了host(如 foo.bar.com),那么只有使用 foo.bar.com 这个域名进行访问的请求才能匹配
- path 路径,比如访问
http://192.168.56.200:80/testpath
就能匹配上上面的规则 - backend 后端,意思是想把这个请求转发给哪个 service 。
要想让 ingress 资源工作,集群必须要有一个 ingress 控制器
# kubectl apply -f ingress-nginx-controller.yaml
apiVersion: v1
kind: Namespace
metadata:
name: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
---
kind: ConfigMap
apiVersion: v1
metadata:
name: nginx-configuration
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
---
kind: ConfigMap
apiVersion: v1
metadata:
name: tcp-services
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
---
kind: ConfigMap
apiVersion: v1
metadata:
name: udp-services
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: nginx-ingress-serviceaccount
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: nginx-ingress-clusterrole
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
rules:
- apiGroups:
- ""
resources:
- configmaps
- endpoints
- nodes
- pods
- secrets
verbs:
- list
- watch
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- apiGroups:
- ""
resources:
- services
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- "extensions"
- "networking.k8s.io"
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- "extensions"
- "networking.k8s.io"
resources:
- ingresses/status
verbs:
- update
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
name: nginx-ingress-role
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
rules:
- apiGroups:
- ""
resources:
- configmaps
- pods
- secrets
- namespaces
verbs:
- get
- apiGroups:
- ""
resources:
- configmaps
resourceNames:
# Defaults to "<election-id>-<ingress-class>"
# Here: "<ingress-controller-leader>-<nginx>"
# This has to be adapted if you change either parameter
# when launching the nginx-ingress-controller.
- "ingress-controller-leader-nginx"
verbs:
- get
- update
- apiGroups:
- ""
resources:
- configmaps
verbs:
- create
- apiGroups:
- ""
resources:
- endpoints
verbs:
- get
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
name: nginx-ingress-role-nisa-binding
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: nginx-ingress-role
subjects:
- kind: ServiceAccount
name: nginx-ingress-serviceaccount
namespace: ingress-nginx
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: nginx-ingress-clusterrole-nisa-binding
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: nginx-ingress-clusterrole
subjects:
- kind: ServiceAccount
name: nginx-ingress-serviceaccount
namespace: ingress-nginx
---
kind: Service
apiVersion: v1
metadata:
name: ingress-nginx
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
clusterIP: 10.1.211.240
externalTrafficPolicy: Cluster
ports:
- name: http
nodePort: 31686
port: 80
protocol: TCP
targetPort: http
- name: https
nodePort: 30036
port: 443
protocol: TCP
targetPort: https
selector:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
sessionAffinity: None
type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-ingress-controller
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
template:
metadata:
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
annotations:
prometheus.io/port: "10254"
prometheus.io/scrape: "true"
spec:
# wait up to five minutes for the drain of connections
terminationGracePeriodSeconds: 300
serviceAccountName: nginx-ingress-serviceaccount
containers:
- name: nginx-ingress-controller
image: registry.aliyuncs.com/google_containers/nginx-ingress-controller:0.26.1
args:
- /nginx-ingress-controller
- --configmap=(POD_NAMESPACE)/nginx-configuration
- --tcp-services-configmap=(POD_NAMESPACE)/tcp-services
- --udp-services-configmap=(POD_NAMESPACE)/udp-services
- --publish-service=(POD_NAMESPACE)/ingress-nginx
- --annotations-prefix=nginx.ingress.kubernetes.io
securityContext:
allowPrivilegeEscalation: true
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
# www-data -> 33
runAsUser: 33
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- name: http
containerPort: 80
- name: https
containerPort: 443
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 10
readinessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 10
lifecycle:
preStop:
exec:
command:
- /wait-shutdown
---
然后再创建一个 web 的 deployment 对象
$ kubectl create deployment web --image=registry.cn-beijing.aliyuncs.com/qingfeng666/hello-app:1.0
然后将 deployment 暴露出来
$ kubectl expose deployment web --type=NodePort --port=8080
创建 ConfigMap 对象
ConfigMap 里可以设定一系列的配置,方便其他 Pod 直接引用导入。
# kubectl create -f configmap-multikeys.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: special-config
namespace: default
data:
SPECIAL_LEVEL: very
SPECIAL_TYPE: charm
那怎么引用呢?使用的是 envFrom 这个关键词。
# kubectl create -f pod-configmap-envFrom.yaml
apiVersion: v1
kind: Pod
metadata:
name: dapi-test-pod
spec:
containers:
- name: test-container
image: k8s.gcr.io/busybox
command: [ "/bin/sh", "-c", "env" ]
envFrom:
- configMapRef:
name: special-config
restartPolicy: Never
DaemenSet
一种应用通常会有多个副本同时运行,正常情况下,这些副本会随机分布在各个 Node 上,有的 Node 可能会有一个甚至多个的副本,在大多数场景下,这样的调度结果并不是我们所预期的。
我们希望的是,在每个 node 上只会运行一个副本。
要实现的这样的效果,就要借助 DaemenSet 了。
其实在 Kubernetes 中就用 DaemonSet 来运行系统组件,你可以通过如下命令查得
kubectl get daemonset --namespace=kube-system
如果想学习 DaemonSet 的编排,可以直接 kubectl edit
去查看
kc edit daemonset kube-proxy --namespace=kube-system
Job
容器按照持续运行的时间可分为两类:服务类容器和工作类容器。
- 服务类容器:需要一直运行,比如 http
- 工作类容器:可能是一次性的任务,结束后退出
下面是一个 Job 类型的简单任务:myjob.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: myjob
spec:
template:
spec:
containers:
- name: myjob
image: busybox
command: ["echo", "Hello, K8S~"]
restartPolicy: Never
通过 kubectl apply -f myjob.yaml
运行后,就可以通过 kubectl get job
命令查看其状态
[root@master ~]# kubectl get job
NAME COMPLETIONS DURATION AGE
myjob 1/1 88s 2m34s
job 虽然是一次的任务,但也是一个 pod,同样可以通过 kubectl get pod
查看
[root@master ~]# kc get po
NAME READY STATUS RESTARTS AGE
myjob-mxkns 0/1 Completed 0 2m45s
这个 job 任务输出了一行内容,可以通过 kubectl logs
查看
[root@master ~]# kubectl logs myjob-mxkns
Hello, K8S~
一个任务太慢,如果想多个任务同时运行,就像开启多线程那样,可以用上这两个参数 completions
和 parallelism
- parallelism:指同时可以运行多少个 job ,可以理解为线程数
- completions:指整个 job 的结束条件,必须要满足有多少个 pod 运行成功,大任务才能结束退出
比如 completions 设置为 4 ,parallelism 设置为2 ,那么每次运行会开启两个 job 或者 pod,结束后,再开启两个 job 或者 pod,此时已完成的 job 达到 4,满足预设的结束条件,整个大任务退出。
apiVersion: batch/v1
kind: Job
metadata:
name: myjob
spec:
completions: 4
parallelism: 2
template:
spec:
containers:
- name: myjob
image: busybox
command: ["echo", "Hello, K8S~"]
restartPolicy: Never
CronJob
Job 是一次性任务,如果想实现定时任务呢,可以用 CronJob。
定时计划的编写规则,和 Linux 上的 Crond 一样。
下面我写了一个每分钟打印一行 Hello, K8S~
的定时任务
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: myjob
spec:
schedule: "*/1 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: myjob
image: busybox
command: ["echo", "Hello, K8S~"]
restartPolicy: OnFailure
使用如下命令可以查看定时任务
[root@master ~]# kubectl get cronjob
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
myjob */1 * * * * False 0 34s 2m50s
经过观察,每过一分钟,就会创建一个 pod 。如果过了一天,你发现 kubectl get po
有一大堆的 job po 怎么办?如何实现自动清理呢?可以设置一下 successfulJobsHistoryLimit
参数,指定要保留运行成功的 pod 数
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: myjob
spec:
schedule: "*/1 * * * *"
successfulJobsHistoryLimit: 3
jobTemplate:
spec:
template:
spec:
containers:
- name: myjob
image: busybox
command: ["echo", "Hello, K8S~"]
restartPolicy: OnFailure
其他概念
标签:label
默认配置下,Scheduler 会将 Pod 调度到所有可用的 Node。不过有些情况我们希望将 Pod 部署到指定的 Node,比如将有大量磁盘 I/O 的 Pod 部署到配置了 SSD 的 Node;或者 Pod 需要 GPU,需要运行在配置了 GPU 的节点上。
Kubernetes 是通过 label 来实现这个功能的。
label 是 key-value 对,各种资源都可以设置 label,灵活添加各种自定义属性。比如执行如下命令标注 node1 是配置了 SSD 的节点。
kubectl label node node1 disktype=ssd
然后再使用如下命令,可以查看 node 上的 label
kubectl get node --show-labels
有了 label 后,如何在创建资源对象时,指定这些标签呢?
K8S 使用的是 nodeSelector
这个属性来指定的
apiVersion: v1
kind: Pod
metadata:
name: myapp
labels:
name: myapp
spec:
containers:
- name: nginx
image: nginx:1.7.9
nodeSelector:
disktype: ssd
如果想要移除一个资源上的 label,可以执行如下命令
kubectl label node node1 disktype-
如果想要修改一个资源上的 label,只要加上 --overwrite
即可
kubectl label node node1 disktype=ssd --overwrite
流程理解
滚动更新
一个应用若有多个副本同时运行,在更新升级的时候,为了不影响服务,通常会使用滚动更新的方式进行升级,一次只升级一个副本,成功后,再升级另一个副本。
下面来给大家演示一下
我先准备一个 3 副本的 Deployment httpd.yaml
文件
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpd
spec:
selector:
matchLabels:
run: httpd
replicas: 3
template:
metadata:
labels:
run: httpd
spec:
containers:
- name: httpd
image: httpd:2.2.31
ports:
- containerPort: 80
通过 kubectl apply 进行部署,此时的 httpd 是 2.3.1 的版本
再使用 vi 编辑 httpd.yaml ,把 httpd 的版本改成 2.2.32 后,再次执行 kubectl apply 进行部署,再次查看,image 已经刷新成 2.2.32 版本
但是 pod 的更新需要时间, deployment 下 image 的版本是 2.2.32,并不代表所有的 pod 都已经升级成功。
通过 kubectl describe deployment httpd
可以看到整个更新的详细过程。
- 用 2.2.32 去创建第一个新的 pod,再删除一个 2.2.31 的 pod
- 用 2.2.32 去创建第二个新的 pod,再删除一个 2.2.31 的 pod
- 用 2.2.32 去创建第三个新的 pod,再删除一个 2.2.31 的 pod
- 更新完成
回退版本
kubectl apply
每次更新应用时 Kubernetes 都会记录下当前的配置,保存为一个 revision(版次),通过 kc rollout history deployment httpd
这条命令可以查看历史的 revision。
从上面的截图来看,有几个问题:
- revision 只有序号,用户根本不知道 1 和 2 分别对应哪个版本
- 默认情况下,Kubernetes 只会保留最近几个 revision。
对此,相应的解决方法是:
- 不同的 revision 使用不同的 yaml,比如 httpd.v1.yaml 和 httpd.v2.yaml,然后在 apply 的时候加上
--record
参数,就能把整个命令记录在 CHANGE-CAUSE 里,如此一来就能区分版本了。 - 通过在 yaml 中指定
.spec.revisionHistoryLimit
项来指定保留多少旧的ReplicaSet,0 代表永远不保留,这里你可以设置大一些。
下面来演示一下
可以看到 CHANGE-CAUSE 里已经有了信息
现在我要回退到 revision=1 这个版本,可以执行下面这条命令
kubectl rollout undo deployment httpd --to-revision=1
再用 kubectl describe
查看,可以看到连版本回退也是滚动回退的
再次查看 revision ,会发现 v1 的序号由 1 变成了 3.
健康检查
每个容器应用都有可能发生故障,这种故障具体可以表现为两种形式
- 程序意外退出,返回码非零
- 程序无法提供服务,比如 web 服务返回 500 内部错误
Kubernetes 之所以如此强大,一部分是因为它的自愈能力非常的出色,当它检查到有如上两种情况时,并且你的 .spec.restartPolicy = OnFailure
时就会重启我们的 Pod。
对于这第一种形式,程序意外退出,返回码非零的,这是 Kubernetes 默认的健康检查机制,一般不需要我们进行配置。
而第二种形式,情况就比较复杂了,服务不可用的场景可太多了,具体如何检测呢?根本不能有一个统一的标准,于是 Kubernetes 干脆给出接口,让你来自定义检测方法。
根据目的的不同,探测方法可以分为两种:
- 通过Liveness 探测:设置的是达成 Failure 的条件,判断是否需要重启实现自愈
- 通过 Readiness 探测:设置的是达成 Ready 的条件,判断是否准备好对外提供服务,在滚动更新中有妙用
在滚动更新中,有两个非常重要的参数
.spec.strategy.rollingUpdate.maxSurge
:设置的是副本数的最大上限,可以是整数,也可以是百分比.spec.strategy.rollingUpdate.maxUnavailable
:设置的是不可用副本数的上限,可以是整数,也可以是百分比
具体的案例,可以查看 在滚动更新中使用 Health Check
相关命令
$ kubectl get configmap -n kube-system
$ kubectl describe configmap -n kube-system
# 查看 k8s 系统空间的服务
$ kubectl get po -n kube-system
# 进入pod
$ kubectl exec -it <pod_name> sh
# 获取 pod
$ kubectl get po
# 查看pod信息
$ kubectl describe pod nginx
# 查看 service
$ kubectl get svc
#查看某个 service 信息
$ kubectl describe svc <svc_name>
# 删除命令
$ kubectl delete po <pod>
$ kubectl delete svc <service>
$ kubectl delete deployment <deployment>
$ kubectl delete ing <ingress>
# 更改命令
$ kubectl edit po <pod>
$ kubectl edit svc <service>
$ kubectl edit deployment <deployment>
$ kubectl edit ing <ingress>
查看日志输出
# 查看一个pod的所有输出
kubectl logs mypod --all-containers=true
# 查看一个pod的某容器的输出 kubectl logs <pod_name> -c <container_name>
# 如果不指定容器,那么只会选一个输出
kubectl logs <pod_name>
# 以 tail -f 的方式去查看日志 kubectl logs mypod --all-containers=true -f
标签操作
# 加标签
kubectl label nodes <node_name> <key=value>
# 查看标签 kubectl get nodes --show-labels
查看pod 的ip地址,如果有标签的话,可以再加个-l key=value
来过滤
$ kubectl get pods -o yaml | grep podIP
环境变量
下面这是一个可以用来验证环境变量的 yaml 。
apiVersion: v1
kind: Pod
metadata:
name: dependent-envars-demo
spec:
containers:
- name: dependent-envars-demo
args:
- printf UNCHANGED_REFERENCE=UNCHANGED_REFERENCE'\n'; printf SERVICE_ADDRESS=SERVICE_ADDRESS'\n';printf ESCAPED_REFERENCE=ESCAPED_REFERENCE'\n'; command:
- sh
- -c
image: busybox
env:
- name: SERVICE_PORT
value: "80"
- name: SERVICE_IP
value: "172.17.0.1"
- name: UNCHANGED_REFERENCE
value: "(PROTOCOL)://(SERVICE_IP):(SERVICE_PORT)"
- name: PROTOCOL
value: "https"
- name: SERVICE_ADDRESS
value: "(PROTOCOL)://(SERVICE_IP):(SERVICE_PORT)"
- name: ESCAPED_REFERENCE
value: "(PROTOCOL)://(SERVICE_IP):$(SERVICE_PORT)"
运行后,可以得出结论
- 在 env 中定义的变量,是有依赖关系的:意味着,你不能在一个变量还没定义前就引用它,否则将是一个普通的字符串。
- 如果你在一个变量中想表示
${VAR_NAME}
这个字符串,需要再在前面加一个$
进行转义,也即$${VAR_NAME}
- 上面的
value
中的变量名,必须使用$(VAR)
的形式书写,括号不能省略。在 command 或者 args 字段中 也有类似的要求。
env:
- name: MESSAGE
value: "hello world"
command: ["/bin/echo"]
args: ["$(MESSAGE)"]
cgroup
cd /sys/fs/cgroup/cpu
创建一个目录,在里面有一个cpu.cfs_quota_us,使用 Echo 填入 20000,也就是 20% 的cpu时间,然后再入 task,往里面写入 进程id,就会限制 这个进程id的cpu使用时间,实现cpu的配额。
对应到 docker 中
# .2 也就是 0.2 的意思
$ docker run -it --cpu=".2" nginx /bin/sh
运行后,再进入 /sys/fs/cgroup/cpu 查看cpu.cfs_quota_us,就是 50000 了。
编写技巧
如果你使用 VS Code,可以装一个叫做 Kubernets 的插件,装完后,只要你先输入 kind:
再输入你要创建的对象类型,就可以自动导入模板
选中后回车,就变成这样子,你再这个模板的基础上填填补补就好啦~ 这样就方便多了。
5. 部署 Dashboard
先准备如下 yaml 文件 kubernetes-dashboard.yaml
# Copyright 2017 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
apiVersion: v1
kind: Namespace
metadata:
name: kubernetes-dashboard
---
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
---
kind: Service
apiVersion: v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
spec:
type: NodePort
ports:
- port: 443
targetPort: 8443
nodePort: 31111
selector:
k8s-app: kubernetes-dashboard
---
apiVersion: v1
kind: Secret
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard-certs
namespace: kubernetes-dashboard
type: Opaque
---
apiVersion: v1
kind: Secret
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard-csrf
namespace: kubernetes-dashboard
type: Opaque
data:
csrf: ""
---
apiVersion: v1
kind: Secret
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard-key-holder
namespace: kubernetes-dashboard
type: Opaque
---
kind: ConfigMap
apiVersion: v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard-settings
namespace: kubernetes-dashboard
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
rules:
# Allow Dashboard to get, update and delete Dashboard exclusive secrets.
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["kubernetes-dashboard-key-holder", "kubernetes-dashboard-certs", "kubernetes-dashboard-csrf"]
verbs: ["get", "update", "delete"]
# Allow Dashboard to get and update 'kubernetes-dashboard-settings' config map.
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["kubernetes-dashboard-settings"]
verbs: ["get", "update"]
# Allow Dashboard to get metrics.
- apiGroups: [""]
resources: ["services"]
resourceNames: ["heapster", "dashboard-metrics-scraper"]
verbs: ["proxy"]
- apiGroups: [""]
resources: ["services/proxy"]
resourceNames: ["heapster", "http:heapster:", "https:heapster:", "dashboard-metrics-scraper", "http:dashboard-metrics-scraper"]
verbs: ["get"]
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
rules:
# Allow Metrics Scraper to get metrics from the Metrics server
- apiGroups: ["metrics.k8s.io"]
resources: ["pods", "nodes"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: kubernetes-dashboard
subjects:
- kind: ServiceAccount
name: kubernetes-dashboard
namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kubernetes-dashboard
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: kubernetes-dashboard
subjects:
- kind: ServiceAccount
name: kubernetes-dashboard
namespace: kubernetes-dashboard
---
kind: Deployment
apiVersion: apps/v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
spec:
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
k8s-app: kubernetes-dashboard
template:
metadata:
labels:
k8s-app: kubernetes-dashboard
spec:
containers:
- name: kubernetes-dashboard
image: registry.cn-beijing.aliyuncs.com/qingfeng666/dashboard:v2.0.4
imagePullPolicy: Always
ports:
- containerPort: 8443
protocol: TCP
args:
- --auto-generate-certificates
- --namespace=kubernetes-dashboard
# Uncomment the following line to manually specify Kubernetes API server Host
# If not specified, Dashboard will attempt to auto discover the API server and connect
# to it. Uncomment only if the default does not work.
# - --apiserver-host=http://my-address:port
volumeMounts:
- name: kubernetes-dashboard-certs
mountPath: /certs
# Create on-disk volume to store exec logs
- mountPath: /tmp
name: tmp-volume
livenessProbe:
httpGet:
scheme: HTTPS
path: /
port: 8443
initialDelaySeconds: 30
timeoutSeconds: 30
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsUser: 1001
runAsGroup: 2001
volumes:
- name: kubernetes-dashboard-certs
secret:
secretName: kubernetes-dashboard-certs
- name: tmp-volume
emptyDir: {}
serviceAccountName: kubernetes-dashboard
nodeSelector:
"kubernetes.io/os": linux
# Comment the following tolerations if Dashboard must not be deployed on master
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
---
kind: Service
apiVersion: v1
metadata:
labels:
k8s-app: dashboard-metrics-scraper
name: dashboard-metrics-scraper
namespace: kubernetes-dashboard
spec:
ports:
- port: 8000
targetPort: 8000
selector:
k8s-app: dashboard-metrics-scraper
---
kind: Deployment
apiVersion: apps/v1
metadata:
labels:
k8s-app: dashboard-metrics-scraper
name: dashboard-metrics-scraper
namespace: kubernetes-dashboard
spec:
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
k8s-app: dashboard-metrics-scraper
template:
metadata:
labels:
k8s-app: dashboard-metrics-scraper
annotations:
seccomp.security.alpha.kubernetes.io/pod: 'runtime/default'
spec:
containers:
- name: dashboard-metrics-scraper
image: registry.cn-beijing.aliyuncs.com/qingfeng666/metrics-scraper:v1.0.4
ports:
- containerPort: 8000
protocol: TCP
livenessProbe:
httpGet:
scheme: HTTP
path: /
port: 8000
initialDelaySeconds: 30
timeoutSeconds: 30
volumeMounts:
- mountPath: /tmp
name: tmp-volume
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsUser: 1001
runAsGroup: 2001
serviceAccountName: kubernetes-dashboard
nodeSelector:
"kubernetes.io/os": linux
# Comment the following tolerations if Dashboard must not be deployed on master
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
volumes:
- name: tmp-volume
emptyDir: {}
然后执行命令创建
kubectl apply -f kubernetes-dashboard.yaml
查看服务
kubectl get svc -n kubernetes-dashboard
此时访问如下地址,在界面上输入 thisisunsafe
,会跳转到 token 登陆界面
https://192.168.56.201:31111
然后到集群中输入如下命令,获取 token
kubectl -n kube-system describe $(kubectl -n kube-system get secret -n kube-system -o name | grep namespace) | grep token
使用阿里云的镜像源
要想使用阿里云的镜像源,首先你得有阿里云帐号,然后再开通容器镜像服务
链接:https://cr.console.aliyun.com/cn-shenzhen/instances/images