Kubernetes

Kubernetes

K8s集群架构

Master主控节点

  • API Server

是集群的统一入口,都以RestFul风格交给Etcd存储。提供认证、授权、访问控制、API注册和发现等机制

  • Scheduler

节点调度,选择node节点应用部署

  • controller-manager

处理集群中常规后台任务,一个资源对应一个控制器

  • Etcd

储存系统,用于保存集群相关的数据

Node工作节点

  • kubelet

Master节点指派到Node的代表,用于管理本机容器

  • kube-proxy

提供网络代理,负载均衡等

K8s核心概念

Pod

  • pod是K8s中最小的执行单元
  • pod是一组容器的集合,其中的容器共享一个网络
  • 生命周期十分短暂,服务器重启之后就找不到了。

Volume

  • 申明在Pod容器中可访问的文件目录
  • 可以挂载在Pod中多个容器的指定路径下
  • 支持多种后端储存抽象,本地存储、分布式存储、云存储

Label

  • 用于对象资源查询,筛选

Controller

  • 确保预期的Pod副本数量
  • 实现Pod的无状态部署(Deployment)和有状态部署(StatefulSet)
  • 可以让多个Node运行同一个Pod

Deployment

  • 定义一组Pod副本数目,版本等
  • 通过控制器维持Pod数目(自动修复,回收失败的Pod)
  • 通过控制器以指定的策略控制版本(回滚,滚动升级等)

Service

  • 定义一组Pod的访问规则,为一个或多个Pod提供稳定的访问地址

K8s流程

  • 通过Kubectl提交一个创建RC(Replication Controller)的请求,该请求通过APl server写入etcd
  • 此时Controller Manager通过API Server的监听资源变化的接口监听到此RC事件
  • Controller Manager分析之后,发现当前集群中还没有它所对应的Pod实例,于是根据RC里的Pod模板定义一个生成Pod对象,通过API Server写入etcd
  • 此事件被Scheduler发现,它立即执行执行一个复杂的调度流程,为这个新的Pod选定一个落户的Node,然后通过API Server讲这一结果写入etcd中
  • 目标Node上运行的Kubelet进程通过API Server监测到这个新生的Pod.并按照它的定义,启动该Pod并任劳任怨地负责它的下半生,直到Pod的生命结束
  • 随后,我们通过Kubectl提交一个新的映射到该Pod的Service的创建请求
  • ControllerManager通过Label标签查询到关联的Pod实例,然后生成Service的Endpoints信息,并通过APIServer写入到etcd中
  • 接下来,所有Node上运行的Proxy进程通过API Server查询并监听Service对象与其对应的Endpoints信息,建立一个软件方式的负载均衡器来实现Service访问到后端Pod的流量转发功能

K8s集群搭建

视频搭建:k8s教程由浅入深-尚硅谷_哔哩哔哩_bilibili

博客借鉴:使用kubeadm方式搭建K8S集群 (moguit.cn)

注意,因为我穷买不起那么多服务器来搭建集群,于是通过VMWare软件创建了三个”服务器”,使用镜像为CentOS-7-x86_64-Minimal-2009.iso,分配硬件资源最好每台都能是2C4G往上,即使内存不够用可以降为2G,但处理器一定要2C往上,这是K8s的最低标准

有两种方式搭建集群,kubeadm和二进制包。Kubeadm 降低部署门槛,但屏蔽了很多细节,遇到问题很难排查。如果想更容易可控,推荐使用二进制包部署Kubernetes 集群,虽然手动部署麻烦点,期间可以学习很多工作原理,也利于后期维护。

使用kubeadm方式搭建K8S集群

kubeadm是官方社区推出的一个用于快速部署kubernetes集群的工具。

这个工具能通过两条指令完成一个kubernetes集群的部署:

1
2
3
4
5
# 创建一个 Master 节点
kubeadm init

# 将一个 Node 节点加入到当前集群中
kubeadm join <Master节点的IP和端口 >

=以下ip请以虚拟机内分配的主机ip为准:==

输入“ip addr”并按回车键确定,发现无法获取IP(CentOS 7默认没有ifconfig命令),记录下网卡名称(本例中为ens33)。

1
2
vi /etc/sysconfig/network-scripts/ifcfg-ens33
修改文件中ONBOOT=no为ONBOOT=yes
1
2
service network restart	#重启网路服务
ip addr #获取本机IP,之后就可以直接在XShell中登陆操作了

安装要求

在开始之前,部署Kubernetes集群机器需要满足以下几个条件:

  • 一台或多台机器,操作系统 CentOS7.x-86_x64
  • 硬件配置:2GB或更多RAM,2个CPU或更多CPU,硬盘30GB或更多【注意master需要两核】
  • 可以访问外网,需要拉取镜像,如果服务器不能上网,需要提前下载镜像并导入节点
  • 禁止swap分区

准备环境

角色 IP
master 192.168.177.130
node1 192.168.177.131
node2 192.168.177.132

然后开始在每台机器上执行下面的命令

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
# 关闭防火墙
systemctl stop firewalld
systemctl disable firewalld

# 关闭selinux
# 永久关闭
sed -i 's/enforcing/disabled/' /etc/selinux/config
# 临时关闭
setenforce 0

# 关闭swap
# 临时
swapoff -a
# 永久关闭
sed -ri 's/.*swap.*/#&/' /etc/fstab

# 根据规划设置主机名【master节点上操作】
hostnamectl set-hostname k8smaster
# 根据规划设置主机名【node1节点操作】
hostnamectl set-hostname k8snode1
# 根据规划设置主机名【node2节点操作】
hostnamectl set-hostname k8snode2

# 在master添加hosts
cat >> /etc/hosts << EOF
192.168.177.130 k8smaster
192.168.177.131 k8snode1
192.168.177.132 k8snode2
EOF


# 将桥接的IPv4流量传递到iptables的链
cat > /etc/sysctl.d/k8s.conf << EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
# 生效
sysctl --system

# 时间同步
yum install ntpdate -y
ntpdate time.windows.com

安装Docker/kubeadm/kubelet

所有节点安装Docker/kubeadm/kubelet ,Kubernetes默认CRI(容器运行时)为Docker,因此先安装Docker

安装Docker

首先配置一下Docker的阿里yum源

1
2
3
4
5
6
7
8
cat >/etc/yum.repos.d/docker.repo<<EOF
[docker-ce-edge]
name=Docker CE Edge - \$basearch
baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/7/\$basearch/edge
enabled=1
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg
EOF

然后yum方式安装docker

1
2
3
4
5
6
7
8
9
# yum安装
yum -y install docker-ce

# 查看docker版本
docker --version

# 启动docker
systemctl enable docker
systemctl start docker

配置docker的镜像源

1
2
3
4
5
cat >> /etc/docker/daemon.json << EOF
{
"registry-mirrors": ["https://b9pmyelo.mirror.aliyuncs.com"]
}
EOF

然后重启docker

1
systemctl restart docker

添加kubernetes软件源

然后我们还需要配置一下yum的k8s软件源

1
2
3
4
5
6
7
8
9
cat > /etc/yum.repos.d/kubernetes.repo << EOF
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF

安装kubeadm,kubelet和kubectl

由于版本更新频繁,这里指定版本号部署:

1
2
3
4
# 安装kubelet、kubeadm、kubectl,同时指定版本
yum install -y kubelet-1.18.0 kubeadm-1.18.0 kubectl-1.18.0
# 设置开机启动
systemctl enable kubelet

部署Kubernetes Master【master节点】

在 192.168.177.130 执行,也就是master节点

1
kubeadm init --apiserver-advertise-address=192.168.177.130 --image-repository registry.aliyuncs.com/google_containers --kubernetes-version v1.18.0 --service-cidr=10.96.0.0/12  --pod-network-cidr=10.244.0.0/16

由于默认拉取镜像地址k8s.gcr.io国内无法访问,这里指定阿里云镜像仓库地址,【执行上述命令会比较慢,因为后台其实已经在拉取镜像了】,我们 docker images 命令即可查看已经拉取的镜像

image-20200929094302491

当我们出现下面的情况时,表示kubernetes的镜像已经安装成功

image-20200929094620145

使用kubectl工具 【master节点操作】

1
2
3
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

执行完成后,我们使用下面命令,查看我们正在运行的节点

1
kubectl get nodes

image-20200929094933142

能够看到,目前有一个master节点已经运行了,但是还处于未准备状态

下面我们还需要在Node节点执行其它的命令,将node1和node2加入到我们的master节点上

加入Kubernetes Node【Slave节点】

下面我们需要到 node1 和 node2服务器,执行下面的代码向集群添加新节点

执行在Master节点Kubernetes初始化成功之后的命令:

1
2
kubeadm join 192.168.177.130:6443 --token 8j6ui9.gyr4i156u30y80xf \
--discovery-token-ca-cert-hash sha256:eda1380256a62d8733f4bddf926f148e57cf9d1a3a58fb45dd6e80768af5a500

注意,以上的命令是在master初始化完成后,每个人的都不一样!!!需要复制自己生成的

默认token有效期为24小时,当过期之后,该token就不可用了。这时就需要重新创建token,操作如下:

1
kubeadm token create --print-join-command

当我们把两个节点都加入进来后,我们就可以去Master节点 执行下面命令查看情况

1
kubectl get node

image-20201113165358663

token过期后,node还需要加入master就需要重新获得token和ca证书sha256编码hash值

1
2
3
4
5
6
7
$ kubeadm token create

W1020 17:31:05.411260 22815 configset.go:202] WARNING: kubeadm cannot validate component configs for API groups [kubelet.config.k8s.io kubeproxy.config.k8s.io]
rl7uvf.hcarslhzos1lfd5r
$ openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'

11049fe03b790733f3e9e23199e35259d1d871d0d97cd4727250d31a312cc5ba

在node上执行

1
kubeadm join 192.168.71.136:6443 --token rl7uvf.hcarslhzos1lfd5r --discovery-token-ca-cert-hash sha256:11049fe03b790733f3e9e23199e35259d1d871d0d97cd4727250d31a312cc5ba

部署CNI网络插件

上面的状态还是NotReady,下面我们需要网络插件,来进行联网访问

1
2
# 下载网络插件配置
wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

默认镜像地址无法访问,sed命令修改为docker hub镜像仓库。

1
2
3
4
5
# 添加
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

# 查看状态 【kube-system是k8s中的最小单元】
kubectl get pods -n kube-system

运行后的结果

image-20201113165929510

运行完成后,我们查看状态可以发现,已经变成了Ready状态了

image-20201113194557147

如果上述操作完成后,还存在某个节点处于NotReady状态,可以在Master将该节点删除

1
2
3
4
5
6
7
# master节点将该节点删除
kubectl delete node k8snode1

# 然后到k8snode1节点进行重置
kubeadm reset
# 重置完后在加入
kubeadm join 192.168.177.130:6443 --token 8j6ui9.gyr4i156u30y80xf --discovery-token-ca-cert-hash sha256:eda1380256a62d8733f4bddf926f148e57cf9d1a3a58fb45dd6e80768af5a500

测试kubernetes集群

我们都知道K8S是容器化技术,它可以联网去下载镜像,用容器的方式进行启动

在Kubernetes集群中的Master中创建一个pod,验证是否正常运行:

1
2
3
4
# 下载nginx 【会联网拉取nginx镜像】
kubectl create deployment nginx --image=nginx
# 查看状态
kubectl get pod

如果我们出现Running状态的时候,表示已经成功运行了

image-20201113203537028

下面我们就需要将端口暴露出去,让其它外界能够访问

1
2
3
4
# 暴露端口
kubectl expose deployment nginx --port=80 --type=NodePort
# 查看一下对外的端口
kubectl get pod,svc

能够看到,我们已经成功暴露了 80端口 到 30529上

image-20201113203840915

我们到我们的宿主机浏览器上,访问如下地址

1
http://192.168.177.130:30529/

发现我们的nginx已经成功启动了

image-20201113204056851

到这里为止,我们就搭建了一个单master的k8s集群

image-20201113204158884

错误汇总

错误一

在执行Kubernetes init方法的时候,出现这个问题

1
2
error execution phase preflight: [preflight] Some fatal errors occurred:
[ERROR NumCPU]: the number of available CPUs 1 is less than the required 2

是因为VMware设置的核数为1,而K8S需要的最低核数应该是2,调整核数重启系统即可

错误二

我们在给node1节点使用 kubernetes join命令的时候,出现以下错误

1
2
error execution phase preflight: [preflight] Some fatal errors occurred:
[ERROR Swap]: running with swap on is not supported. Please disable swap

错误原因是我们需要关闭swap

1
2
3
4
5
# 关闭swap
# 临时
swapoff -a
# 临时
sed -ri 's/.*swap.*/#&/' /etc/fstab

错误三

在给node1节点使用 kubernetes join命令的时候,出现以下错误

1
The HTTP call equal to 'curl -sSL http://localhost:10248/healthz' failed with error: Get http://localhost:10248/healthz: dial tcp [::1]:10248: connect: connection refused

解决方法,首先需要到 master 节点,创建一个文件

1
2
3
4
5
6
7
8
9
10
11
# 创建文件夹
mkdir /etc/systemd/system/kubelet.service.d

# 创建文件
vim /etc/systemd/system/kubelet.service.d/10-kubeadm.conf

# 添加如下内容
Environment="KUBELET_SYSTEM_PODS_ARGS=--pod-manifest-path=/etc/kubernetes/manifests --allow-privileged=true --fail-swap-on=false"

# 重置
kubeadm reset

然后删除刚刚创建的配置目录

1
rm -rf $HOME/.kube

然后 在master重新初始化

1
kubeadm init --apiserver-advertise-address=202.193.57.11 --image-repository registry.aliyuncs.com/google_containers --kubernetes-version v1.18.0 --service-cidr=10.96.0.0/12  --pod-network-cidr=10.244.0.0/16

初始完成后,我们再到 node1节点,执行 kubeadm join命令,加入到master

1
2
kubeadm join 202.193.57.11:6443 --token c7a7ou.z00fzlb01d76r37s \
--discovery-token-ca-cert-hash sha256:9c3f3cc3f726c6ff8bdff14e46b1a856e3b8a4cbbe30cab185f6c5ee453aeea5

添加完成后,我们使用下面命令,查看节点是否成功添加

1
kubectl get nodes

错误四

我们再执行查看节点的时候, kubectl get nodes 会出现问题

1
Unable to connect to the server: x509: certificate signed by unknown authority (possibly because of "crypto/rsa: verification error" while trying to verify candidate authority certificate "kubernetes")

这是因为我们之前创建的配置文件还存在,也就是这些配置

1
2
3
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

我们需要做的就是把配置文件删除,然后重新执行一下

1
rm -rf $HOME/.kube

然后再次创建一下即可

1
2
3
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

这个问题主要是因为我们在执行 kubeadm reset 的时候,没有把 $HOME/.kube 给移除掉,再次创建时就会出现问题了

错误五

安装的时候,出现以下错误

1
Another app is currently holding the yum lock; waiting for it to exit...

是因为yum上锁占用,解决方法

1
yum -y install docker-ce

kubectl

1
kubectl [command] [type] [name] [flags]
  • command:指定要对资源执行的操作,例如create、get、describe、delete
  • type:指定资源类型,资源类型是大小写敏感的,开发者能够以单数 、复数 和 缩略的形式
  • name:指定资源的名称,名称也是大小写敏感的,如果省略名称,则会显示所有的资源
  • flags:指定可选的参数,例如,可用 -s 或者 -server参数指定Kubernetes API server的地址和端口

通过==kubectl –help==获取详细命令,==kubectl get –help==获取使用范例和详细用法

1
2
3
4
5
6
7
8
# 创建一个nginx镜像
kubectl create deployment nginx --image=nginx

# 对外暴露端口
kubectl expose deployment nginx --port=80 --type=NodePort

# 查看资源
kubectl get pod, svc

基础命令

常见的基础命令

命令 介绍
create 通过文件名或标准输入创建资源
expose 将一个资源公开为一个新的Service
run 在集群中运行一个特定的镜像
set 在对象上设置特定的功能
get 显示一个或多个资源
explain 文档参考资料
edit 使用默认的编辑器编辑一个资源
delete 通过文件名,标准输入,资源名称或标签来删除资源

部署命令

命令 介绍
rollout 管理资源的发布
rolling-update 对给定的复制控制器滚动更新
scale 扩容或缩容Pod数量,Deployment、ReplicaSet、RC或Job
autoscale 创建一个自动选择扩容或缩容并设置Pod数量

集群管理命令

命令 介绍
certificate 修改证书资源
cluster-info 显示集群信息
top 显示资源(CPU/M)
cordon 标记节点不可调度
uncordon 标记节点可被调度
drain 驱逐节点上的应用,准备下线维护
taint 修改节点taint标记

故障和调试命令

命令 介绍
describe 显示特定资源或资源组的详细信息
logs 在一个Pod中打印一个容器日志,如果Pod只有一个容器,容器名称是可选的
attach 附加到一个运行的容器
exec 执行命令到容器
port-forward 转发一个或多个
proxy 运行一个proxy到Kubernetes API Server
cp 拷贝文件或目录到容器中
auth 检查授权

其它命令

命令 介绍
apply 通过文件名或标准输入对资源应用配置
patch 使用补丁修改、更新资源的字段
replace 通过文件名或标准输入替换一个资源
convert 不同的API版本之间转换配置文件
label 更新资源上的标签
annotate 更新资源上的注释
completion 用于实现kubectl工具自动补全
api-versions 打印受支持的API版本
config 修改kubeconfig文件(用于访问API,比如配置认证信息)
help 所有命令帮助
plugin 运行一个命令行插件
version 打印客户端和服务版本信息

Yaml

通过yaml文件可以创建一个pod,而yaml文件比较复杂,我们可以基于拉取的远程镜像的yaml或导出现有Pod的yaml进行自定义修改

1
kubectl create deployment web --image=nginx -o yaml --dry-run > mynginx.yaml

==–dry-run==表明只是拉取而不是真正创建Pod,将其yaml文件导出至mynginx.yaml

1
kubectl get deploy nginx -o=yaml > mynginx.yaml

这个则是直接导出现有的Pod的yaml文件到mynginx.yaml

Pod

Pod是K8S系统中可以创建和管理的最小单元,是资源对象模型中由用户创建或部署的最小资源对象模型,也是在K8S上运行容器化应用的资源对象,其它的资源对象都是用来支撑或者扩展Pod对象功能的,比如控制器对象是用来管控Pod对象的,Service或者Ingress资源对象是用来暴露Pod引用对象的,PersistentVolume资源对象是用来为Pod提供存储等等

K8S不会直接处理容器,而是Pod,Pod是由一个或多个container组成。其中有一个被称为“根容器”的Pause容器,其镜像来源于Kubernetes平台。

创建容器使用docker,一个docker对应一个容器,一个容器运行一个应用进程。Pod是多进程设计,运用多个应用程序,也就是一个Pod里面有多个容器,而一个容器里面运行一个应用程序

为什么不直接操作容器?

  • Kubernetes并不是只支持Docker这一个容器运行。

Kubernetes通过CRI这个抽象层,支持除Docker之外的其他容器运行时,比如rkt甚至支持客户自定义容器运行时。因此,借助CRI这个抽象层,使得Kubernetes不依赖于底层某一种具体的容器运行时实现技术,而是直接操作pod,pod内部再管理多个业务上紧密相关的用户业务容器,这种架构便于Kubernetes做扩展

  • 可以定义一组容器的状态

假设Kubernetes没有pod的概念,而是直接管理容器,那么一组容器作为一个单元,假设其中一个容器死亡了,此时这个单元的状态应该如何定义呢?应该理解成整体死亡,还是个别死亡?

这也是每个pod里都有一个Kubernetes系统自带的pause容器的原因,通过引入pause这个与业务无关并且作用类似于Linux操作系统守护进程的Kubernetes系统标准容器,以pause容器的状态来代表整个容器组的状态

  • 共享网络,频繁通信

pod里所有的业务容器共享pause容器的IP地址,以及pause容器mount的Volume,通过这种设计,业务容器之间可以直接通信,文件也能够直接彼此共享。

Kubernetes里的每个pod都有唯一的IP地址。Pod的IP地址可以通过命令kubectl describe pod来查看。也就意味着Kubernetes的底层网络可以借助Flannel,openswitch等虚拟二层网络技术来实现集群内任意两个pod之间的TCP/IP通信。即使是不同主机间的Pod。

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
apiVersion: v1     #必选,版本号,例如v1
kind: Pod   #必选,资源类型,例如 Pod
metadata:   #必选,元数据
name: string #必选,Pod名称
namespace: string #Pod所属的命名空间,默认为"default"
labels:    #自定义标签列表
string: string  
spec: #必选,Pod中容器的详细定义
containers: #必选,Pod中容器列表
- name: string #必选,容器名称
image: string #必选,容器的镜像名称
imagePullPolicy: [ Always|Never|IfNotPresent ] #获取镜像的策略
command: [string] #容器的启动命令列表,如不指定,使用打包时使用的启动命令
args: [string] #容器的启动命令参数列表
workingDir: string #容器的工作目录
volumeMounts: #挂载到容器内部的存储卷配置
- name: string #引用pod定义的共享存储卷的名称,需用volumes[]部分定义的的卷名
mountPath: string #存储卷在容器内mount的绝对路径,应少于512字符
readOnly: boolean #是否为只读模式
ports: #需要暴露的端口库号列表
- name: string #端口的名称
containerPort: int #容器需要监听的端口号
hostPort: int #容器所在主机需要监听的端口号,默认与Container相同
protocol: string #端口协议,支持TCP和UDP,默认TCP
env: #容器运行前需设置的环境变量列表
- name: string #环境变量名称
value: string #环境变量的值
resources: #资源限制和请求的设置
limits: #资源限制的设置
cpu: string #Cpu的限制,单位为core数,将用于docker run --cpu-shares参数
memory: string #内存限制,单位可以为Mib/Gib,将用于docker run --memory参数
requests: #资源请求的设置
cpu: string #Cpu请求,容器启动的初始可用数量
memory: string #内存请求,容器启动的初始可用数量
lifecycle: #生命周期钩子
postStart: #容器启动后立即执行此钩子,如果执行失败,会根据重启策略进行重启
preStop: #容器终止前执行此钩子,无论结果如何,容器都会终止
livenessProbe: #对Pod内各容器健康检查的设置,当探测无响应几次后将自动重启该容器
exec:   #对Pod容器内检查方式设置为exec方式
command: [string] #exec方式需要制定的命令或脚本
httpGet: #对Pod内个容器健康检查方法设置为HttpGet,需要制定Path、port
path: string
port: number
host: string
scheme: string
HttpHeaders:
- name: string
value: string
tcpSocket: #对Pod内个容器健康检查方式设置为tcpSocket方式
port: number
initialDelaySeconds: 0 #容器启动完成后首次探测的时间,单位为秒
timeoutSeconds: 0    #对容器健康检查探测等待响应的超时时间,单位秒,默认1秒
periodSeconds: 0    #对容器监控检查的定期探测时间设置,单位秒,默认10秒一次
successThreshold: 0
failureThreshold: 0
securityContext:
privileged: false
restartPolicy: [Always | Never | OnFailure] #Pod的重启策略
nodeName: <string> #设置NodeName表示将该Pod调度到指定到名称的node节点上
nodeSelector: obeject #设置NodeSelector表示将该Pod调度到包含这个label的node上
imagePullSecrets: #Pull镜像时使用的secret名称,以key:secretkey格式指定
- name: string
hostNetwork: false #是否使用主机网络模式,默认为false,如果设置为true,表示使用宿主机网络
volumes: #在该pod上定义共享存储卷列表
- name: string #共享存储卷名称 (volumes类型有很多种)
emptyDir: {} #类型为emtyDir的存储卷,与Pod同生命周期的一个临时目录。为空值
hostPath: string #类型为hostPath的存储卷,表示挂载Pod所在宿主机的目录
path: string    #Pod所在宿主机的目录,将被用于同期中mount的目录
secret:    #类型为secret的存储卷,挂载集群与定义的secret对象到容器内部
scretname: string
items:
- key: string
path: string
configMap: #类型为configMap的存储卷,挂载预定义的configMap对象到容器内部
name: string
items:
- key: string
path: string

Pod类型

Pod是K8S集群中所有业务类型的基础,可以把Pod看作运行在K8S集群上的小机器人,不同类型的业务就需要不同类型的小机器人去执行。目前K8S的业务主要可以分为以下几种

  • 长期伺服型:long-running
  • 批处理型:batch
  • 节点后台支撑型:node-daemon
  • 有状态应用型:stateful application

上述的几种类型,分别对应的小机器人控制器为:Deployment、Job、DaemonSet 和 StatefulSet

Pod实现机制

共享网络

当Pod中的容器处于同一Namespace时即可实现网络共享。

以共享网络方式实现Pod,会先在Pod中创建一个根容器==Pause容器==,然后在创建业务容器时会把它放到==info容器==中,而info容器中会独立出ip、mac、port等信息,这样业务容器就会被划分到一个共享网络中。

共享存储

如果Node宕机,为了继续运行Pod的服务,需要在另一个Node里运行这个Pod。但是如果通过拉取新镜像来复原服务环境,就会丢失原Node的数据。因而,将Pod内的container数据实现持久化存储,即挂载数据卷。

Pod镜像拉取

  • IfNotPresent:默认值,镜像在宿主机上不存在才拉取
  • Always:每次创建Pod都会重新拉取一次镜像
  • Never:Pod永远不会主动拉取这个镜像

Pod资源限制

新生的Pod要由Sheduler调度到合适的Node中,其中资源限制就是调度算法的考虑点。

  • requests:容器正常运行最低要求的资源数,也就是对Node配置的最低要求。
  • limits:限制容器扩张,是Node最多能分配给容器使用的资源量。

Pod重启机制

  • Always:当容器终止退出后,总是重启容器,默认策略 【nginx等,需要不断提供服务】
  • OnFailure:当容器异常退出(退出状态码非0)时,才重启容器。
  • Never:当容器终止退出,从不重启容器 【批量任务,只执行一次】

Pod健康检查

两种检查后的处理策略:

  • ==livenessProbe(存活检查)==:如果检查出错,将杀死容器,再根据Pod的重启策略操作
  • ==readinessProbe(就绪检查)==:如果检查出错,K8s会将Pod从Service endpoint中剔除,并用一个相同的Pod代替。

三种检查方式:

  • ==http Get==:发送HTTP请求,返回200 - 400 范围状态码为成功
  • ==exec==:执行Shell命令返回状态码是0为成功
  • ==tcpSocket==:发起TCP Socket建立成功

Pod调度

Pod节点选择器

Pod调度除了Sheduler分配,还能够人为调度。env_role后面选择的是Node,dev为Node的别名

1
kubectl label node k8snode1 env_role=dev	#该命令可以为Node设置别名

Pod节点亲和性

除了Pod资源限制会影响调度,Pod还可以要求Node的其他属性符合。根据该要求的强硬程度分为硬亲和性和软亲和性。

  • ==operator==:判断是否满足亲和性的操作符。In就要求Pod要调度到标签为dev、test的Node中。类似的操作符还有NotIn、Exists、Gt、Lt、DoesNotExists

污点和污点容忍

可以为Node设置污点值,但调度时就会通过污点值来判断能否分配Pod到该Node中。污点值有三:

  • NoSchedule:一定不被调度
  • PreferNoSchedule:尽量不被调度
  • NoExecute:不会调度,并且还会驱逐Node已有Pod

查看Node污点:

1
kubectl describe node k8snode1 | grep Taint

添加污点:

1
2
kubectl taint node k8snode1 env_role=tzq:NoSchedule
#kubectl taint node k8snode1 key=value:污点值

删除污点:

1
kubectl taint node k8snode1 env_role:NoSchedule-

我们可以在node上添加污点用于拒绝pod调度上来,但是如果就是想将一个pod调度到一个有污点的node上去,这时候应该怎么做呢?这就要使用到容忍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Pod
metadata:
name: pod-toleration
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
tolerations: # 添加容忍
- key: "env_role" # 要容忍的污点的key
operator: "Equal" # 操作符
value: "tzq" # 容忍的污点的value
effect: "NoExecute" # 添加容忍的规则,这里必须和标记的污点规则相同

我们为Pod添加污点容忍,这样就可以分配到env_role=tzq:NoSchedule的Node中去了。

Controller

博客借鉴:yooome/LearningNotes

Pod控制器是管理pod的中间层,使用Pod控制器之后,只需要告诉Pod控制器,想要多少个什么样的Pod就可以了,它会创建出满足条件的Pod并确保每一个Pod资源处于用户期望的目标状态。如果Pod资源在运行中出现故障,它会基于指定策略重新编排Pod。

在kubernetes中,有很多类型的pod控制器,每种都有自己的适合的场景,常见的有下面这些:

  • ReplicaSet:保证副本数量一直维持在期望值,并支持pod数量扩缩容,镜像版本升级
  • Deployment:通过控制ReplicaSet来控制Pod,并支持滚动升级、回退版本
  • Horizontal Pod Autoscaler:可以根据集群负载自动水平调整Pod的数量,实现削峰填谷
  • DaemonSet:在集群中的指定Node上运行且仅运行一个副本,一般用于守护进程类的任务
  • Job:它创建出来的pod只要完成任务就立即退出,不需要重启或重建,用于执行一次性任务
  • Cronjob:它创建的Pod负责周期性任务控制,不需要持续后台运行
  • StatefulSet:管理有状态应用

Replication Controller

Replication Controller 保证了在所有时间内,都有特定数量的Pod副本正在运行,如果太多则会按照template新建几个。

Pod template

一个Replication Controller通过模版来创建pod,这个是Replication Controller对象内置的功能,但是我们计划要将这个功能从Replication Controller剥离开来

与其说Pod的模版是一个多有Pod副本的期望状态,Pod的模版更像是一个饼干的模具,一旦从模具中出来之后,饼干就和饼干模具没啥关系了,没有任何关联。而Replication Controller创建的pod可以在之后直接的修改。

Labels

由Replication Controller监控的Pod的数量是是由一个叫 selector(标签选择器)决定的,selector在Replication Controller和被控制的pod创建了一个松散耦合的关系,与pod相比,pod与他们的定义文件关系紧密。

因为Replication Controller是通过selector链接Pod的,因此由template创造出来的Pod必须拥有selector的标签。虽然Replication Controller也可以有自己的标签,这只是用来被Controller-manager标识的

所以,删除一个Pod可以通过改变其标签,令Replication Controller失去该Pod,这样改Pod就会被自动的替换掉。反过来,如果想要清空Replication Controller所管辖的Pods,可以将.spec.replicas设置为零。这与直接删除Replication Controller不同,直接删除是不会影响已经被创造出来的Pods的

ReplicaSet(rs)

ReplicaSet的资源清单文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
apiVersion: apps/v1 # 版本号
kind: ReplicaSet # 类型
metadata: # 元数据
name: # rs名称
namespace: # 所属命名空间
labels: #标签
controller: rs
spec: # 详情描述
replicas: 3 # 副本数量
selector: # 选择器,通过它指定该控制器管理哪些pod
matchLabels: # Labels匹配规则
app: nginx-pod
matchExpressions: # Expressions匹配规则
- {key: app, operator: In, values: [nginx-pod]}
template: # 模板,当副本数量不足时,会根据下面的模板创建pod副本
metadata:
labels:
app: nginx-pod
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- containerPort: 80
  • replicas:指定副本数量,其实就是当前rs创建出来的pod的数量,默认为1

  • selector:选择器,它的作用是建立pod控制器和pod之间的关联关系,采用的Label Selector机制

    在pod模板上定义label,在控制器上定义选择器,就可以表明当前控制器能管理哪些pod了

  • template:模板,就是当前控制器创建pod所使用的模板


1
2
3
4
5
6
7
8
9
10
11
$ kubectl create -f pc-replicaset.yaml	#通过配置文件创建控制器

#DESIRED:期望的副本数 CURRENT:当前副本数 READY:准备提供服务的副本数
$ kubectl get rs pc-replicaset -n dev -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
pc-replicaset 3 3 3 22s nginx nginx:1.17.1 app=nginx-pod

#在kubernetes删除RS前,会将RS的replicas调整为0,等待所有的Pod被删除后,在执行RS对象的删除
$ kubectl delete rs pc-replicaset -n dev
#推荐直接删除配置文件
$ kubectl delete -f pc-replicaset.yaml

扩缩容

1
2
3
4
#先更改配置文件的replicas,再运行
$ kubectl edit rs pc-replicaset -n dev
#也可以命令实现
$ kubectl scale rs pc-replicaset --replicas=2 -n dev

镜像升级

1
2
3
4
#更改配置文件的image,再运行
$ kubectl edit rs pc-replicaset -n dev
#也可以命令实现
$ kubectl set image rs pc-replicaset nginx=nginx:1.17.1 -n dev

Deployment(deploy)

Deployment不直接管理pod,而是通过管理ReplicaSet来简介管理Pod,即:Deployment管理ReplicaSet,ReplicaSet管理Pod。所以Deployment比ReplicaSet功能更加强大。

相比于ReplicaSet,Deployment多了以下配置:

1
2
3
4
5
6
7
8
9
spec: # 详情描述
revisionHistoryLimit: 3 # 保留历史版本
paused: false # 暂停部署,默认是false
progressDeadlineSeconds: 600 # 部署超时时间(s),默认是600
strategy: # 策略
type: RollingUpdate|Recreate # 滚动更新策略|代替原有pod策略
rollingUpdate: # 为滚动更新策略时才生效
maxSurge: 30% # 滚动升级的过程中,最大可以超出期望Pod数的副本数,可以为百分比(默认25%),也可以为整数
maxUnavailable: 30% # 滚动升级的过程中,不可用状态的 Pod 的最大值,可以为百分比(默认25%),也可以为整数

1
2
3
4
5
6
7
8
$ kubectl create -f pc-deployment.yaml --record=true

#UP-TO-DATE 最新版本的Pod数量 AVAILABLE 当前可用的Pod数量
$ kubectl get deploy pc-deployment -n dev
NAME READY UP-TO-DATE AVAILABLE AGE
pc-deployment 3/3 3 3 15s

$ kubectl delete -f pc-deployment.yaml

扩缩容、镜像更新

扩缩容方式和ReplicSet一致,只是将操作符从rs换成deploy

1
2
3
kubectl scale deployment pc-deployment --replicas 10
#如果集群支持 horizontal pod autoscaling 的话,还可以为Deployment设置自动扩展:
kubectl autoscale deployment pc-deployment --min=10 --max=15 --cpu-percent=80

镜像更新则扩展了重建更新和滚动更新,通过strategy.type指定策略类型,支持两个属性RollingUpdateRecreate

1
kubectl set image deployment/pc-deployment nginx=nginx:1.17.2 #更新时会按照Depolyment的strategy.type来决定使用哪种更新策略

==滚动更新==:更新时新版本的pod的创建不会影响旧版本pod的运行,新版本创建成功后才会删除旧版本

==重建更新==:旧版本被删除,一直等到新版本上线

版本回退

kubectl rollout: 版本升级相关功能,支持下面的选项:

  • status 显示当前升级状态
  • history 显示 升级历史记录
  • pause 暂停版本升级过程
  • resume 继续已经暂停的版本升级过程
  • restart 重启版本升级过程
  • undo 回滚到上一级版本(可以使用–to-revision回滚到指定版本)
1
kubectl rollout undo deployment pc-deployment --to-revision=2

金丝雀发布

比如有一批新的Pod资源创建完成后立即暂停更新过程,此时,仅存在一部分新版本的应用,主体部分还是旧的版本。然后,再筛选一小部分的用户请求路由到新版本的Pod应用,继续观察能否稳定地按期望的方式运行。确定没问题之后再继续完成余下的Pod资源滚动更新,否则立即回滚更新操作。这就是所谓的金丝雀发布。

Job

Job的配置清单

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
apiVersion: batch/v1 # 版本号
kind: Job # 类型
metadata: # 元数据
name: # rs名称
namespace: # 所属命名空间
labels: #标签
controller: job
spec: # 详情描述
completions: 1 # 指定job需要成功运行Pods的次数。默认值: 1
parallelism: 1 # 指定job在任一时刻应该并发运行Pods的数量。默认值: 1
activeDeadlineSeconds: 30 # 指定job可运行的时间期限,超过时间还未结束,系统将会尝试进行终止。
backoffLimit: 6 # 指定job失败后进行重试的次数。默认是6
manualSelector: true # 是否可以使用selector选择器选择pod,默认是false
selector: # 选择器,通过它指定该控制器管理哪些pod
matchLabels: # Labels匹配规则
app: counter-pod
matchExpressions: # Expressions匹配规则
- {key: app, operator: In, values: [counter-pod]}
template: # 模板,当副本数量不足时,会根据下面的模板创建pod副本
metadata:
labels:
app: counter-pod
spec:
restartPolicy: Never # 重启策略只能设置为Never或者OnFailure
containers:
- name: counter
image: busybox:1.30
command: ["bin/sh","-c","for i in 9 8 7 6 5 4 3 2 1; do echo $i;sleep 2;done"]

Job负责批量处理短暂的一次性任务 (short lived one-off tasks),即仅执行一次的任务,它保证批处理任务的一个或多个Pod成功结束。

Kubernetes支持以下几种Job,通过根据.spec.completions.spec.Parallelism的设置可以划分:

Job类型 使用示例 行为 completions Parallelism
一次性Job 数据库迁移 创建一个Pod直至其成功结束 1 1
固定结束次数的Job 处理工作队列的Pod 依次创建一个Pod运行直至completions个成功结束 2+ 1
固定结束次数的并行Job 多个Pod同时处理工作队列 依次创建多个Pod运行直至completions个成功结束 2+ 2+
并行Job 多个Pod同时处理工作队列 创建一个或多个Pod直至有一个成功结束 1 2+

CronJob(CJ)

CronJob控制器以Job控制器资源为其管控对象,并借助它管理pod资源对象,Job控制器定义的作业任务在其控制器资源创建之后便会立即执行,但CronJob可以以类似于Linux操作系统的周期性任务作业计划的方式控制其运行时间点重复运行的方式。也就是说,CronJob可以在特定的时间点(反复的)去运行job任务

CRonJob的资源清单相比于Job多了以下部分,将Job的配置转移至了.spec.jobTemplate

1
2
3
4
5
6
7
8
9
10
11
spec: # 详情描述
schedule: "*/1 * * * *" # cron格式的作业调度运行时间点,用于控制任务在什么时间执行
concurrencyPolicy: # 并发执行策略,用于定义前一次作业运行尚未完成时是否以及如何运行后一次的作业
failedJobHistoryLimit: 1 # 为失败的任务执行保留的历史记录数,默认为1
successfulJobHistoryLimit: 3 # 为成功的任务执行保留的历史记录数,默认为3
startingDeadlineSeconds: # 启动作业错误的超时时长
jobTemplate: # job控制器模板,用于为cronjob控制器生成job对象;下面其实就是job的定义
metadata:
spec:
compeletions: 1
parallelism: 1

schedule:*用于指定任务的执行时间。 **多个时间可以用逗号隔开; 范围可以用连字符给出;\可以作为通配符; /表示每…

1
2
*/1    *      *    *     *
<分钟> <小时> <日> <月份> <星期>

Service

每个pod都由自己的ip,这些IP也随着时间的变化也不能持续依赖。这样就引发了一个问题:如果一些Pods(让我们叫它作后台,后端)提供了一些功能供其它的Pod使用(让我们叫作前台),在kubernete集群中是如何实现让这些前台能够持续的追踪到这些后台的?

Kubernete Service 是一个定义了一组Pod的策略的抽象,我们也有时候叫做宏观服务。这些被服务标记的Pod都是(一般)通过label Selector决定的。Service会对提供同一个服务的多个pod进行聚合,并且提供一个统一的入口地址。通过访问Service的入口地址就能访问到后面的pod服务。

img

kube-proxy

Service在很多情况下只是一个概念,真正起作用的其实是kube-proxy服务进程,每个Node节点上都运行着一个kube-proxy服务进程。当创建Service的时候会通过api-server向etcd写入创建的service的信息,而kube-proxy会基于监听的机制发现这种Service的变动,然后它会将最新的Service信息转换成对应的访问规则

kube-proxy目前支持三种工作模式:

==userspace==

userspace模式下,kube-proxy会为每一个Service创建一个监听端口,发向Cluster IP的请求被Iptables规则重定向到kube-proxy监听的端口上,kube-proxy根据LB算法选择一个提供服务的Pod并和其建立链接,以将请求转发到Pod上。 该模式下,kube-proxy充当了一个四层负责均衡器的角色。由于kube-proxy运行在userspace中,在进行转发处理时会增加内核和用户空间之间的数据拷贝,虽然比较稳定,但是效率比较低。

img

==iptables==

iptables模式下,kube-proxy为service后端的每个Pod创建对应的iptables规则,直接将发向Cluster IP的请求重定向到一个Pod IP。 该模式下kube-proxy不承担四层负责均衡器的角色,只负责创建iptables规则。该模式的优点是较userspace模式效率更高,但不能提供灵活的LB策略,当后端Pod不可用时也无法进行重试。

img

==ipvs==

ipvs模式和iptables类似,kube-proxy监控Pod的变化并创建相应的ipvs规则。ipvs相对iptables转发效率更高。除此以外,ipvs支持更多的LB算法。

此模式必须开启ipvs内核,否则会降级成iptables模式

1
2
3
4
# 开启ipvs
[root@k8s-master01 ~]# kubectl edit cm kube-proxy -n kube-system
# 修改mode: "ipvs"
[root@k8s-master01 ~]# kubectl delete pod -l k8s-app=kube-proxy -n kube-system

img

Service类型

Service资源清单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
kind: Service  # 资源类型
apiVersion: v1 # 资源版本
metadata: # 元数据
name: service # 资源名称
namespace: dev # 命名空间
spec: # 描述
selector: # 标签选择器,用于确定当前service代理哪些pod
app: nginx
type: ClusterIP # Service类型,指定service的访问方式,默认ClusterIP
clusterIP: # 虚拟服务的ip地址,只能在集群内部访问
sessionAffinity: # session亲和性,支持ClientIP、None两个选项
ports: # 端口信息
- protocol: TCP
port: 3017 # service端口
targetPort: 5003 # pod端口
nodePort: 31122 # 主机端口,将Service通过指定的Node上的端口暴露给外部,通过此方法,就可以在集群外部访问服务

.spec.type的可选值:

  • ClusterIP:默认值,它是Kubernetes系统自动分配的虚拟IP,只能在集群内部访问
  • NodePort:将Service通过指定的Node上的端口暴露给外部,通过此方法,就可以在集群外部访问服务
  • LoadBalancer:使用外接负载均衡器完成到服务的负载分发,注意此模式需要外部云环境支持
  • ExternalName: 把集群外部的服务引入集群内部,直接使用

ClusterIP

创建deployment.yaml,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: apps/v1
kind: Deployment
metadata:
name: pc-deployment
namespace: dev
spec:
replicas: 3
selector:
matchLabels:
app: nginx-pod
template:
metadata:
labels:
app: nginx-pod
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- containerPort: 80

创建改deployment后,查看pod分配:

1
2
3
4
5
[root@k8s-master01 ~]# kubectl get pods -n dev -o wide --show-labels
NAME READY STATUS IP NODE LABELS
pc-deployment-66cb59b984-8p84h 1/1 Running 10.244.1.39 node1 app=nginx-pod
pc-deployment-66cb59b984-vx8vx 1/1 Running 10.244.2.33 node2 app=nginx-pod
pc-deployment-66cb59b984-wnncx 1/1 Running 10.244.1.40 node1 app=nginx-pod

配置service-clusterip.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
name: service-clusterip
namespace: dev
spec:
selector:
app: nginx-pod
clusterIP: 10.97.97.97 # service的ip地址,如果不写,默认会生成一个
type: ClusterIP
ports:
- port: 80 # Service端口
targetPort: 80 # pod端口

创建改service,会发现改service统一管理了标签为app: nginx-pod的pods。

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
# 创建service
[root@k8s-master01 ~]# kubectl create -f service-clusterip.yaml
service/service-clusterip created

# 在这里有一个Endpoints列表,里面就是当前service可以负载到的服务入口
[root@k8s-master01 ~]# kubectl describe svc service-clusterip -n dev
Name: service-clusterip
Namespace: dev
Labels: <none>
Annotations: <none>
Selector: app=nginx-pod
Type: ClusterIP
IP: 10.97.97.97
Port: <unset> 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.1.39:80,10.244.1.40:80,10.244.2.33:80
Session Affinity: None
Events: <none>

# 也可以查看ipvs的映射规则
[root@k8s-master01 ~]# ipvsadm -Ln
TCP 10.97.97.97:80 rr #rr表示采用轮询访问
-> 10.244.1.39:80 Masq 1 0 0
-> 10.244.1.40:80 Masq 1 0 0
-> 10.244.2.33:80 Masq 1 0 0

我们可以看到,一个Service管理了三个Pod的通信,外部可以通过访问10.244.1.39:80来访问Pod1的服务,也可以通过10.97.97.97:80来访问Pod1的服务,只不过要注意Service采用的是轮询方式,第一次访问到Pod1,再一次就会访问Pod2了。

EndPoint

Endpoint是kubernetes中的一个资源对象,存储在etcd中,用来记录一个service对应的所有pod的访问地址。一个Service由一组Pod组成,这些Pod通过Endpoints暴露出来,Endpoints是实现实际服务的端点集合

image-20200509191917069

正如上面提到的,外部统一访问Service时,Service默认会通过轮询的方式把流量转发给后面的Pod,但也可以基于客户端地址保持会话,即同一个客户端的请求交给固定的一个Pod处理。

这种Service负载分发策略由属性.spec.sessionAffinity决定,spec.sessionAffinity = ClusterIP时表示基于客户端地址保持会话。

HeadLiness

ClusterIP提供的两种负载均衡方式难以满足显示要求,而HeadLiness能够自定义负载均衡策略。这类Service不会分配Cluster Ip,如果要访问service,只能通过service的域名进行查询。

创建HeadLiness类型Service,只需要在ClusterIP类型上将spec.clusterIP设置为None即可

NodePort

在之前的样例中,创建的Service的ip地址只有集群内部才可以访问,如果希望将Service暴露给集群外部使用,那么就要使用到另外一种类型的Service,称为NodePort类型。相比于CLusterIP,NodePort会将Service的一个端口映射到Node的真实端口上,这样访问Node对应端口就能访问到Service了

img

K8s的三种IP

  • Node IP:Node节点IP地址
  • Pod IP:Pod的IP地址
  • Cluster IP:Service的IP地址

​ NodeIP是Kubernetes集群中每个节点的物理网卡的IP地址,这是一个真实存在的物理网络,所有属于这个网络的服务器之间都能通过这个网络直接通讯,不管他们中间是否含有不属于Kubernetes集群中的节点。想Kubernetes之外的节点访问Kubernetes集群内的节点或者TCP/IP服务时,必须通过Node IP

 Pod IP是每个Pod的IP地址,通常是一个虚拟的二层网络,用于不同Pod间彼此直接通讯。所以Kubernetes里一个Pod里的容器访问另外一个Pod里的容器,就是通过Pod IP所在的虚拟二层网络进行通信,而真实的TCP/IP流量则是通过Node IP所在的物理网卡流出

​ 而ClusterIP仅仅是个用于统一管理PodIP的虚拟网络,更无法在Kubernets外访问。如果外网想要访问Pods就需要使用NodePort类型的Service来代理PodIP

配置NodePort

创建service-nodeport.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
name: service-nodeport
namespace: dev
spec:
selector:
app: nginx-pod
type: NodePort # service类型
ports:
- port: 80
nodePort: 30002 # 指定绑定的node的端口(默认的取值范围是:30000-32767), 如果不指定,会默认分配
targetPort: 80

创建Service后查看该NodePort-service可以看到被映射到了30002端口,这时使用任意NodeIP:30002都能访问到Pod

NodePort

其实我们也可以发现,我们当初直接创建Pod时为了暴露端口,也用了NodePort

1
2
kubectl create deployment nginx --image=nginx
kubectl expose deployment nginx --port=80 --type=NodePort

LoadBalancer

LoadBalancer和NodePort很相似,目的都是向外部暴露一个端口,区别在于LoadBalancer会在集群的外部再来做一个负载均衡设备,而这个设备需要外部环境支持的,外部服务发送到这个设备上的请求,会被设备负载之后转发到集群中。

img

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
47
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "my-service"
},
"spec": {
"selector": {
"app": "MyApp"
},
"ports": [
{
"protocol": "TCP",
"port": 80,
"targetPort": 9376,
"nodePort": 30061
}
],
"clusterIP": "10.0.171.239",
"loadBalancerIP": "78.11.24.19",
"type": "LoadBalancer"
},
"status": {
"loadBalancer": {
"ingress": [
{
"ip": "146.148.47.155"
}
]
}
}

apiVersion: v1
kind: Service
metadata:
name: service-loadbalancer
namespace: dev
spec:
selector:
app: nginx-pod
type: Loadbalancer # service类型
ports:
- port: 80
nodePort: 30002 # 指定绑定的node的端口(默认的取值范围是:30000-32767), 如果不指定,会默认分配
targetPort: 80
clusterIP: 10.97.97.98
loadBalancerIP: 78.11.24.19

从外部负载均衡器的流量将会被引到后端的Pod,然而具体这个如何实现则要看云提供商。一些云提供商允许指定loadBalancerIP。如果字段loadBalancerIP没有指定,该负载均衡器会被指定一个短暂性的IP。如果指定了loadBalancerIP,但是云提供商不支持这个特性,这个字段会被忽略。

ExternalName

ExternalName类型的Service用于引入集群外部的服务,它通过externalName属性指定外部一个服务的地址,然后在集群内部访问此service就可以访问到外部的服务了。

img

1
2
3
4
5
6
7
8
apiVersion: v1
kind: Service
metadata:
name: service-externalname
namespace: dev
spec:
type: ExternalName # service类型
externalName: www.baidu.com #改成ip地址也可以

Ingress

  • NodePort方式的缺点是会占用很多集群机器的端口,那么当集群服务变多的时候,这个缺点就愈发明显
  • LB方式的缺点是每个service需要一个LB,浪费、麻烦,并且需要kubernetes之外设备的支持

基于这种现状,kubernetes提供了Ingress资源对象,Ingress只需要一个NodePort或者一个LB就可以满足暴露多个Service的需求。工作机制大致如下图表示:

img

1
[root@k8s-master01 ~]

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!