Kubernetes 是一个开源的容器编排平台,可以轻松部署、扩展和管理容器化应用程序。这篇文章是我自学 Kubernetes 的过程,我将过程记录下来。
基础概念
弄清楚 Kubernetes 服务,要先弄清以下几个 Kubernates 的基础概念
Node
一个节点(Node)是一个虚拟机或者物理机
Pod
Pod 是 Kubernetes 最小的调度单元,一个 Pod 可以是一个或者多个容器(container)的组合,他创建了一个容器的运行环境,在这个容器中,容器可以共享一些资源,比如网络,存储以及一些运行时的配置等等。
比如我们有一个应用程序或一个数据库,我们可以将其分别放在两个不同的 pod 中。一个 Pod 运行一个容器,这是一种最佳实践。
当然,一些特殊情况,一个 Pod 也可运行多个容器,这种情况也仅限于这些容器是高度耦合的情况,即我们所说的边车模式 (Sidecar),如下图:
Service
每个 pod 都会有一个集群内部的 IP 地址,外部不可以访问到。pod 并不是一个稳定的实例,十分容易被创建或销毁,当 pod 发生故障时,会被销毁,IP 地址就会变更。这时候就需要 Kubernetes 的 Service 来管理和创建 IP 地址的变更,如下图:
service 上图的 service 连接数据库的 pod 和 App 的 pod,当 App 访问 Service 时,Service 会分配对应的数据库 IP 给 App 进行访问,如下图:
Ingress
当我们服务在本地开发时,可以用 IP 和端口号访问节点,而当部署到生产环境时,则必须配置域名,通过域名来访问
而 Ingress
就是用来管理从集群外部访问集群内部服务的入口和方式的。可以根据 Ingress 规定不同的转发规则,访问集群内部不同的 Service
除此之外,Ingress
还可以配置 SSL 证书、负载均衡等等
ConfigMap
配置完上述描述的配置后,我们就可以提供对外服务了
但还忽略一个问题,例如应用程序的 pod 访问数据库 pod 时,开发中我们会把数据库的地址、端口等连接信息写到配置文件或者环境变量中,然后在应用程序中读取这些信息。这样做,配置信息与应用程序就耦合在一起了。
一旦数据库的地址或者端口发生变化,那我们就要重新编译应用程序,再重新部署到集群中。
为了解决以上问题,Kubernetes 提供了 ConfigMap
的功能,将一些配置信息封装起来,就可以在应用程序中读取和使用了。
Secret
就是 ConfigMap
的延伸,对其里面的内容进行 base64 进行编码。ConfigMap
是明文的,比如我们会配置数据库访问的用户名,密码等,Secret 对这些敏感信息做编码,注意,还不是加密。base64 编码可以通过编程语言进行解析。
User, C.role, Sa
这 3 个分别是身份认证、网络安全、访问控制
Volume
pod 是很容易被销毁的,数据库的 pod 一旦被销毁,其后果可能是灾难性的。K8s 提供了 Volume
组件,他可以将数据库 pod 的数据,同时同步挂载到本地的磁盘上或者集群外部的远程存储上。
这样即使 pod 被销毁,数据也能持久化存储。
Deployment
现在为止,我们还须考虑到应用程序的 pod 高可用姓,比如某个应该程序的节点发生故障,或者节点需要升级或更新维护时,应用程序就会停止服务。这显然是不能接受的。
解决方法是复制多个 Node,并用 Service 进行几种管理,此时 k8s 会自动将请求分散到其他正常工作的节点上。
而 k8s 提供了一个 Deployment
组件,用于管理所有应用程序 pod,他可以定义和管理应用程序的副本数量,以及应用程序的更新策略,简化应用程序的部署和更新操作。如下图:
Deployment
还具备
- 副本控制 ( 就是定义应用的副本数量,如上图,定义了 3 个,如果其中一个副本有故障,那么他会自动创建一个新的副本来替代原有副本,维持副本的数量在 3 个 )
- 滚动更新 ( 可以定义应用程序的升级策略,用于应用程序的版本升级 )
- 自动扩容
等高级功能。
StatefulSet
相较于上面 Deployment
管理应用的副本,数据库其实也需要对应的副本管理工具,这个工具是 StatefulSet
。
和应用不同的是,数据库的每个副本,都是有独立的状态的,而应用是无状态的。简单来说,10 秒前存入的数据的状态,和现在的数据的状态往往是不一样的。所以数据库的 pod,需要把数据同步到其他的副本中,或者把数据写入到一个共享的存储中。
和 Deployment
的功能类似,但 StatefulSet
还保证每个副本都有自己稳定的网络标识符和持久化存储。所以,像数据库,缓存,消息队列等等,以及一些保留了会话状态的应用,都使用 StatefulSet
来部署。
当然,一种更好的实践是将有状态的 pod 从各个节点中剥离出来,进行单独的部署
基础架构
k8s 是一个典型的 master-worker
架构。所以,比如以上所提到的部署了应用和数据库的节点,也被称为工作节点 worker node
。而工作节点通常有多个,master
只有一个,用于管理所有 worker node
工作节点 worker node
为了能提供对外服务,每个 Node 节点上都会包含 3 个对应的服务组件,分别是:
kubelet
,负责管理和维护每个节点上的 pod,也会定期从api-server
组件接受新的 pod 规范,监视工作节点的运行情况,将信息汇报给api-server
。同时也负责 Volume (CVI) 和网络 (CNI) 的管理。kube-proxy
,负责为 pod 提供网络代理和负载均衡。container runtime
容器运行时container runtime
就是我们常说的 Docker-Engine,每个节点都必须有容器运行时,当然,容器运行时也不止 docker,还有如:Containerd
,CRI-O
,Mirantis
通常情况下,一个集群包含多个节点,节点间的通信和负载均衡器就是通过 k-proxy
管理节点 master node
也称作 control panel
从上图可以看出,master 节点的结构和 worker 节点完全不同,他分别包括 API server
,scheduler
,control manager
, etcd
和 cloud control manager
,通过 api server
来管理所有工作节点。
API server
就像一个集群的网关,是整个系统的入口。所有请求都会经过他,再由他分发给不同的组件进行处理。而且所有的组件间也会通过 API server 进行通信。提供认证、授权、访问控制、API 注册和发现等机制
例如,当我们部署一个新应用 pod 时,那么可以用客户端,例如 kubectl
命令行,dashboard
或者其他的 UI 界面工具。
当使用kubectl
命令行创建新的 pod 时,这个请求会先到达 api server 并验证这个请求的合法性,验证通过后再转发给相应的 Scheduler
调度器组件进行处理
Scheduler
负责监控集群中所有节点资源的使用情况,根据一些调度策略,将 pod 调度到合适的节点上运行。
例如下图:
调度器会将 pod 部署时较为空闲的节点上运行
controller manager
控制者管理器,复制管理集群中各种资源对象的状态。比如
- 故障检测。任何一个节点上的 pod 发生故障时,必须有一种机制监测到这个故障,尽快对其进行处理。例如重启该 pod 或者新启用一个 pod 进行替换。
- 自动扩展
- 滚动更新
controller manager 是如何知道哪个节点发生故障的呢?这就需要下面介绍的 etcd
组件
etcd
这个是一个高可用的键值存储系统,类似 Redis
,存储集群中所有资源对象的状态信息。比如哪个 pod
挂掉,哪个 pod
又被新创建了。可以理解为集群的大脑。是整个集群的数据存储中心。
cloud controller manager
云控制器管理器,他是用来和各种云上的 api 进行交互的。而且他能提供一致的管理接口,使得用户可以在不同云平台中管理他们的应用程序。
minicube 搭建单节点环境
本文为了实践 k8s,在本地搭建了个模拟线上的简易版环境来学习 k8s。
minikube 是一个轻量级的 Kubernetes 实现,可在本地计算机上创建虚拟机,并部署仅包含一个节点的简单集群。
一般生产环境的 Kubernetes 集群,一般都是使用云厂商提供的 Kubernetes 服务,如阿里云的 ACK、腾讯云的 TKE、华为云的 CCE 、AWS 的 EKS 等 。他们需要多个节点的集群才能实现高可用。每个节点都是一个服务器或者虚拟机。所以本地开发环境可以使用 minikube 来验证 Kubernetes 的功能。
安装
1 | # macOS |
minikube 常用命令
1 | # 帮助菜单 |
kubectl 命令
安装完成 minikube 之后,附带 kubectl
命令,这个命令就是 Kubernetes 自带的和集群交互的命令
1 | (base) zyzy:~ $ kubectl get nodes |
multipass 虚拟机 + K3s 的多节点集群搭建
minikube 是一个单节点的集群环境,但是稍微复杂一点的环境就不适用了。这里我们用虚拟机和 k3s 技术,模拟一个多节点环境的搭建。
multipass
安装
超级简单,点击这里 multipass 点击相应的版本安装即可
multipass
常用命令
- 创建一个 ubuntu1 虚拟机
1 | (base) zyzy:~ $ multipass launch --name k3s --cpus 2 --memory 4G --disk 10G |
- 查看本机所运行的所有虚拟机
1 | (base) zyzy:~ $ multipass ls |
- 进入 k3s 虚拟机系统
1 | (base) zyzy:~ $ multipass shell k3s |
- 启用虚拟机
1 | (base) zyzy:~ $ multipass start k3s |
- 停止虚拟机
1 | (base) zyzy:~ $ multipass stop k3s |
- 删除虚拟机
1 | (base) zyzy:~ $ multipass delete k3s |
设置 ssh 远程登录 multipass
设置密码登录
在 K3s 虚拟机上操作:
1 | # 登录我们上面设置的k3s虚拟机内部: |
打开并修改以下配置:
1 | -- # PermitRootLogin prohibit-password |
1 | # 重启ssh使其生效 |
设置 ubuntu 用户密码,并退出虚拟机尝试用密码登录:
1 | sudo passwd ubuntu |
在 mac terminal 下查询虚拟机 ip 地址,并运行 ssh 登录虚拟机:
1 | multipass ls |
设置 ssh 公私钥登录
上面的登录步骤每次都要输入密码过于繁琐,我们可以像 GitHub 一样配置公私钥则每次登录都不需要输入密码,具体步骤如下:
本地环境下生成 RSA 密钥对,我在登录 GitHub 时生成过密钥对,可以忽略这一步:
1 | # 生成密钥对,如已生成则略过这一步 |
所有密钥对会放在~/.ssh/文件夹下,显示公钥并复制所有内容
1 | cat ~/.ssh/id_rsa.pub |
重新进入 k3s
虚拟机并粘贴我们上面复制的公钥,注意,要全个文件内容都替换,完成后退出
1 | multipass shell k3s |
在本地将私钥复制到 k3s 虚拟机上:
1 | ssh-copy-id [email protected] |
此时会看到复制成功的提示:
到这一步,已经可以用 ssh 直接登录而无需每次输入密码了,而成功设置了 ssh 登录,则上面的 multipass shell k3s
登录方式则会失效
1 | ssh [email protected] |
设置别名登录
上面每次都要输入 ssh [email protected]
登录比较麻烦,这里可以设置别名登录:
本地电脑上设置 alias
,在 terminal 上输入:
1 | alias k3s="ssh [email protected]" |
该命令只在当前终端有效,如果要长期生效,必须写在~/.zshrc
或 ~/.bash_profile
文件的最后一行,并执行 source 生效
1 | source ~/.zshrc |
k3s
安装
登录进虚拟机后,安装 k3s 的 master 节点:
1 | curl -sfL https://get.k3s.io | sh - |
kubectl
命令验证是否安装成功:
1 | ubuntu@k3s:~$ sudo kubectl get nodes |
创建 worker node
上面我们已经安装好了 master node,下面我们来安装 worker node
先在 master 节点上获取 token,并复制下来
1 | ubuntu@k3s:~$ sudo cat /var/lib/rancher/k3s/server/node-token |
重新开一个 mac 的终端,输入
1 | (base) zyzy:~ $ TOKEN=$(multipass exec k3s sudo cat /var/lib/rancher/k3s/server/node-token) |
可以看到能 echo 打印出相应的 token,证明 token 配置成功,再将 master 的 IP 地址配置到本地并验证
1 | (base) zyzy:~ $ MASTER_IP=$(multipass info k3s | grep IPv4 | awk '{print $2}') |
我们可以继续创建两个 worker 节点并将其配置到 master 节点中
1 | multipass launch --name workder1 --cups 2 --memory 8G --disk 10G |
验证,让我们进入 k3s 的 master 节点中,可以看到 worker1
worker2
节点已经加入到集群中:
1 | (base) zyzy:~ $ multipass shell k3s |
kubectl
管理工具
1 | # 进入k3smaster节点: |
基础命令
1 | # 查看帮助文档 |
创建 kubectl create
及运行kubectl (run | apply)
1 | # 创建并运行一个指定的镜像 |
查看 kubectl (get | describe)
1 | # 其中,RESOURCE可以是以下类型: |
调试 kubectl logs
交互 kubectl exec
比如上面用 replilcaset 创建的 pod,查看日志
1 | sudo kubectl logs nginx-deployment-6d6565499c-bn7gd |
也可以进入 pod 内部查看:
1 | ubuntu@k3s:~$ sudo kubectl exec -it nginx-deployment-6d6565499c-bn7gd -- /bin/bash |
修改,删除和清理资源
1 | # 更新某个资源的标签 |
例如:
1 | # 删除名为nginx的pod |
Deployment
利用 kubectl
创建
以上是创建的的基本命令,但实际操作中,创建一个 pod, 更提倡使用 Deployment 这样的上层资源来创建,具体如下:
1 | # nginx-deployment 为别名 |
查看已创建的 Deployment 资源:
1 | # 查看所有pod,包括非deployment创建的 |
修改 replicaset
副本数量:
上面的 replicaset 是通过 deployment 创建时自动生成的,实际上他是一个管理副本数量的组件
可以通过 edit
来编辑 replicaset 的 ymal 文本
1 | ubuntu@k3s:~$ sudo kubectl edit deployment nginx-deployment |
当输入以上命令后,会自动进入 nginx-deployment 的 yaml 文件进行编辑,我们把replicas
值改为 3 后保存退出
1 | ubuntu@k3s:~$ sudo kubectl get pod |
查看后发现 pod 多了 3 个同属于一个 replicaset 的 pod
利用 yaml 文件创建 (重点)
kubectl
命令行方式创建 pod 可以加入很多参数,例如我们上面举例过的 --image=nginx
,这个参数如果一多或者嵌套的话,那么命令行的方式是非常麻烦的,这时,更推荐利用 yaml
文件的方式创建。
yaml
类似 Docerfile,创建后再用 kubectl
进行调用,pod 就会自动更新了。
具体步骤:
- 创建并编辑 yaml 文件
1 | ubuntu@k3s:~$ vim nginx-deployment.yaml |
编辑如下,下面是一个最基本的 yml 文件
1 | apiVersion: apps/v1 |
apiVersion
: 指定 apiServer 版本,格式是:组别/版本号,group/version,组别经常有 apps(应用), batch(批处理), autoscaling(自动扩缩容) 等等kind
: 用来指定资源对象的类型metadata
: 定义资源对象的元数据,比如资源的名称,标签,命名空间等。spec
: 是 specification 的缩写,定义资源的各种配置信息,包括有多少个副本 (replicas
), 像上面的 spec 有两层嵌套,第一层是定义 deployment 的信息,第二层是定义 Pod 的配置信息
- 经 yml 文件创建创建资源
kubectl create -f
1 | ubuntu@k3s:~$ sudo kubectl create -f nginx-deployment.yaml |
- 修改(
kubectl apply
) 删除 (kubectl delete
)
1 | # 通过文件名或标准输入配置资源 |
Service
利用 kubectl
创建
上面我们用 Deployment 创建了 5 个 pod,我们可以用以下命令查询到其 IP 地址:
1 | ubuntu@k3s:~$ sudo kubectl get pod -o wide |
这些 pod 的 IP 地址都是节点内部地址,无法提供对外服务,所以我们必须创建 Service
并配置其外部服务
创建步骤:
1 | # 创建服务命令 |
查看已经创建服务:
1 | ubuntu@k3s:~$ sudo kubectl get svc |
删除服务:
1 | sudo kubectl delete service nginx-deployment |
利用 yml 文件创建(重点)
1 | vim nginx-service.yaml |
内容如下:
1 | apiVersion: v1 |
type
: 该参数指定了对外端口类型,如果不定义该字段,那么默认是Cluster IP
类型服务,只能在节点内部间访问,NodePort
为对外服务,具体类型参加下表nodePort
: 该参数指定了对外暴露的端口号,范围必须在30000~32767
之间
服务类型 | 描述 |
---|---|
ClusterIP | 默认类型,集群内部的服务 |
NodePort | 节点端口类型,将服务公开到集群节点上 |
LoadBalancer | 负载均衡类型,将服务公开到外部负载均衡器上 |
ExternalName | 外部名称类型,将服务映射到一个外部域名上 |
Headless | 无头类型,主要用于 DNS 解析和服务发现 |
启用:
1 | sudo kubectl apply -f nginx-service.yaml |
查看 pod 的 ip 地址:
1 | ubuntu@k3s:~$ sudo kubectl get nodes -o wide |
复制一个任意一个 worker 节点的 ip,并在浏览器打开,发现打开成功,端口采用我们配置的 nodePort: 30080
portainer 图形界面管理工具
安装及访问
有两种方法:
获取
portainer
的 yaml 文件配置安装,安装在 master 节点- 直接在 master 节点,即我们创建的 k3s 上执行
kubectl apply -n portainer -f https://downloads.portainer.io/ce2-19/portainer.yaml
-n
参数指定 pod 的命名空间,看起来更直观,有隔离不同项目和环境的作用
- 直接在 master 节点,即我们创建的 k3s 上执行
利用
helm
安装,安装在本地helm 是 k8s 的包管理工具,利用以下命令安装
Mac 电脑:
1
2
3
4brew install helm
helm repo add portainer https://portainer.github.io/k8s/
helm update portainer
helm upgrade --install --create-namespace -n portainer portainer portainer/portainer --set tls.force=true
删除
删除 portainer
时,由于我们加了命名空间,所以也要指定命名空间,如下:
1 | sudo kubectl delete namespace portainer |
命名空间 kubectl get ns | namespace
上面提到的命名空间,可以用以下命令查看所有集群里的命名空间
1 | ubuntu@k3s:~$ sudo kubectl get ns |
上面 portainer
是我们指定的命名空间,其余是 k8s 默认创建的
平时创建任何资源如果不指定命名空间,则全部默认放到 default 的命名空间里
从上图可以发现,如创建了命名空间的资源,查看时必须加上 -n
参数
访问
这里我们要输入 master 节点的 IP,而不是 portainer 的 IP,所以本地访问 master 节点的 ip+端口号 30777,本地 master ip 用sudo kubectl get pod -o wide
查找,例如浏览器访问 master 节点:http://192.168.64.3:30777
可以看到访问的页面: