k8s学习记录
最近出于需要,重学了一下k8s。这里做一些过程记录及其总结。
心得
个人认为学习k8s最好的方式就是实践,从跟着教程实践,到最终把自己的真实服务搭建起来。所谓真实的服务,就是真的需要使用的服务,最好是公司的服务。但是开发者可能没有这样的机会,那么退而求其次可以是在自己本机搭建起这些服务,并且保持一直运行,我认为没有机会接触真实集群的开发者,长期在本地维护自己个一个服务群,对学习运维知识很有帮忙。
另外,学习k8s之前,最好有自己搭建服务的经验,了解手动搭建服务的痛点。这样你才能更深刻的体会为什么要有k8s这种工具存在的意义。这样学起来才不会总有一种"为什么要这么麻烦"的烦躁感。
学习过程中,需要化繁为简,分次深入。k8s的概念实在是太多了,很难一次性都了解。有些概念先知道即可,暂时用不到的可以先跳过。像我这种墙内前端,平时连个公网ip都没有,还谈什么LoadBalancer?大概知道是什么,然后什么命令能调用到即可。再深入的理解,可以往后再深入。
要版本化自己的学习,整个学习过程可能会比较长,涉及到很多实践的代码,这些实践的代码最好都跟着手敲一遍。可以本地建个git仓库,把每个实践的代码都放进版本管理里,然后有问题、有心得的时候就写个零碎笔记记录下。
运维发展"野"史
没有考证过整个运维工作的发展历史,拼个人感觉大概讲述下。在k8s之前,大概有分为三个时代:
- 物理机时代。这个阶段的运维需要真实的管理物理机,需要处理电源、网络等等真实的物理机问题。
- 虚拟机时代。后来开始有了虚拟化技术,把真机虚拟成多个虚拟机。这时候,更上层的运维就只需要处理系统安装、服务搭建等问题。估计这就是云服务时代的开端。
- 容器化时代。大概是2013~2014年,Docker问世后,运维进入了容器化时代。容器化,大概解决了反复构建服务环境的问题。也可以说,它解决了服务反复安装的问题。
k8s时代。几乎与Docker同期,k8s紧随Docker问世。它进一步解决了Docker在分布式集群服务里没有解决的问题。两者要做比较的话,我认为容器化更像是描述了一个单体服务的生命周期,而如果要用它来描述一个分布式集群服务,则显得有些乏力。动态扩容过程中相应的网络资源、存储资源、配置资源等等资源调度的工作量也是很大、很繁琐的。如何描述服务与服务之间的关系?如何方便的热更新服务?版本化服务?这些都是仅靠容器化不易解决的问题。而k8正是为描述分布式集群服务而生的。
k8s还是很值得学习的。即便分布式系统被吹得过热的当下,分布式系统的数量依然是比较少见的,大部分服务其实只需要单体服务即可。但是即便数量少,每一个分布式集群都是复杂的,都需要k8s这样的工具来更好的描述其服务的运维工作。而学会之后,甚至回过头来用它来描述传统的单体服务,也会比仅用容器来得更方便,当然也要注意不要过度工程。
k8s的主要概念
先说一些比较实的概念
- cluster(集群)。
- nodes(节点)。这里的nodes,指的就是一台抽象服务器。多个nodes组成一个cluster,当然了一个cluster不单单只有nodes。
- pods。pod的翻译是"豆荚",跟它的作用很呼应。pods是k8s集群里可以操作的最小的计算单元。一个node可以运行多个pods。
- containers(容器)。k8s并不能直接操作创建、销毁容器,容器必须依托在pod里。一个pod可以有多个容器。
下图大概描述了上述几者的关系:
- 在一个cluster里有多个nodes
- 在node1里,有两个pods
- 第一个pod名为wordpress,它有两个容器:一个运行wordpress backend代码,一个运行redis提供给backend使用
- 第二个pod名为cronjob-xxx,看着应该是一个运行cron job的pod,它有两个容器:一个main、一个wait,具体做什么不深究。
再说一些比较虚的概念
- Deployments(部署)。一个Deployment描述了一个部署的生命周期。
- 它描述了pod是什么样子的,即有几个容器,每个容器分别使用什么镜像、什么参数启动、开放什么端口等等信息
- 一个部署需要有几个pod进行负载均衡
- 某个pod挂掉之后,是否需要重启
- …等等等等。
- Services(服务)。我感觉k8s的services是用来描述如何把k8s集群内部的服务(pod组成的)暴露给外部使用。
- Namespace(命名空间)。Namespace可以用来对Deployments、Services、Pods等进行分组,但是Nodes不在其约束范围之内。
k8s实践 – 部署一个guestbook
我基本都是跟着官方的Tutorials进行实践操作的,这是是相关的实践代码。
我拿其中的guestbook来进行转述,同时详细备注一下各个概念yaml文件的结构。
注意:确保你已经安装了minikube作为学习环境,这里不讲述如何安装,详见官方教程。
目标
我们要部署一个guestbook(留言本)网页服务,用户可以再网页上提交留言,后端服务会把它保存到redis。redis服务由一主二从组成,分别负责写与读操作。 这些逻辑代码以及主从配置都是官方都提前准备好的,我们只需要直接使用其镜像即可。
步骤一:部署主redis服务
我们部署一个主redis服务,用来处理写操作。
先描述这个Deployment的样子。创建一个redis-leader-deployment.yaml
文件:
# 文件开头,说明下所要描述的是一个Deployment
apiVersion: apps/v1
kind: Deployment
# 这是一个Deployment的元信息,主要是name跟labels
metadata:
name: redis-leader
labels:
app: redis
role: leader
tier: backend
# spec是specification(规格)的简称,那么对于一个Deployment而言,其产物就是Pods,
# 所以这个规格描述的对象就是Pods,描述Pods如何与Deployment关联、如何创建等信息。
spec:
# 这里声明这个Deployment只会有一个Pod副本
replicas: 1
# spec.selector描述了Pods如何与Deployment进行关联。这里用的是matchLabels的方法,
# 也就是说labels是app=redis,role=leader,tier=backend的所有Pods都是这个Deployment
# 的Pods。我们一般会让Deployment与Pods保持有一个相同的labels子集。所以初次写
# Deployment,你可能会觉得怎么有一大堆重复的labels要声明。
selector:
matchLabels:
app: redis
role: leader
tier: backend
# spec.template描述了Pods如何创建。为什么叫template(模板)?大概是因为一个Deployment可以有
# 多个Pods进行负载均衡吧!
template:
# spec.selector说到的matchLabels只是如何匹配,而这里才是真正定义这个Deployment的Pods的labels的地方
metadata:
labels:
app: redis
role: leader
tier: backend
# Deploment的spec是用来描述Pods的,而Pods的spec则是用来描述其内部的容器的。
spec:
containers:
- name: redis
image: "docker.io/redis:6.0.5"
ports:
- containerPort: 6379
我们把这个服务启动起来,然后watch下pods的创建过程。
> kubectl apply -f redis-leader-deployment.yaml
> kubectl get pods -l app=redis -w
NAME READY STATUS RESTARTS AGE
redis-leader-dd4b55897-b7hxf 0/1 ContainerCreating 0 3s
redis-leader-dd4b55897-b7hxf 1/1 Running 0 84s
# Ctrl-c 退出watch模式,再来看看pods的logs。可以看到这个Deployment已经ready了
> kubectl logs -l app=redis
1:C 14 Nov 2023 12:56:34.373 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1:C 14 Nov 2023 12:56:34.373 # Redis version=6.0.5, bits=64, commit=00000000, modified=0, pid=1, just started
1:C 14 Nov 2023 12:56:34.373 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
1:M 14 Nov 2023 12:56:34.374 * Running mode=standalone, port=6379.
1:M 14 Nov 2023 12:56:34.374 # Server initialized
1:M 14 Nov 2023 12:56:34.374 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
1:M 14 Nov 2023 12:56:34.374 * Ready to accept connections
Deployment启动之后,还需要把它暴露出来。这就需要描述一下它的service结构。新建个redis-leader-service.yaml
文件,内容如下:
apiVersion: v1
kind: Service
metadata:
# service的name会作为它的"网络域名",其它服务用这个名字来访问它。
name: redis-leader
labels:
app: redis
role: leader
tier: backend
# service的spec所描述的,是如何找到pods、以及如何转发暴露pods的服务
spec:
# ports描述了需要转发暴露的端口。
# 对于每一个端口,port表示当前service监听的端口,targetPort表示
# pods暴露出来的端口。当两者一致的时候,可以不写targetPort
ports:
- port: 6379
targetPort: 6379
# 通过matchLabels来找到pods。
# 为什么这里需要特别声明是matchLabels?
selector:
app: redis
role: leader
tier: backend
然后我们apply一下这个service,并且查看一下相关的信息。
> kubectl apply -f redis-leader-service.yaml
service/redis-leader created
# get一下所有services
> kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2d19h
redis-leader ClusterIP 10.97.24.148 <none> 6379/TCP 7s
# 看下这个service的logs。这里没什么好看的,它打印的实际上是pods的logs
> kubectl logs services/redis-leader
1:C 14 Nov 2023 12:56:34.373 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1:C 14 Nov 2023 12:56:34.373 # Redis version=6.0.5, bits=64, commit=00000000, modified=0, pid=1, just started
1:C 14 Nov 2023 12:56:34.373 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
1:M 14 Nov 2023 12:56:34.374 * Running mode=standalone, port=6379.
1:M 14 Nov 2023 12:56:34.374 # Server initialized
1:M 14 Nov 2023 12:56:34.374 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
1:M 14 Nov 2023 12:56:34.374 * Ready to accept connections
# describe里基本是yaml里的内容,另外还带有一些创建后k8s赋予的属性。如IP、IPs这些
> kubectl describe services/redis-leader
Name: redis-leader
Namespace: default
Labels: app=redis
role=leader
tier=backend
Annotations: <none>
Selector: app=redis,role=leader,tier=backend
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.97.24.148
IPs: 10.97.24.148
Port: <unset> 6379/TCP
TargetPort: 6379/TCP
Endpoints: 10.244.0.32:6379
Session Affinity: None
Events: <none>
到此为止,redis-leader这个服务就启动好了。但是redis并没什么好访问的,我们继续后面的工作。yaml的主要结构基本详细描述过了,让我们加快脚步。
步骤二:部署从redis服务
我们新建一个redis-follower.yaml
文件,内容如下:
apiVersion: v1
kind: Service
metadata:
name: redis-follower
labels:
app: redis
role: follower
tier: backend
spec:
ports:
- port: 6379
selector:
app: redis
role: follower
tier: backend
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-follower
labels:
app: redis
role: follower
tier: backend
spec:
# 启动两个pods
replicas: 2
selector:
# 为什么这里不需要写role: follower?
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
role: follower
tier: backend
spec:
containers:
- name: follower
# 为什么这里的镜像跟主redis的镜像不是同一个呢?
image: gcr.io/google_samples/gb-redis-follower:v2
ports:
- containerPort: 6379
一个yaml文件可以用---
分割成多个yaml内容,这样我们可以把service跟deployment写在一起。
这两个从redis pods如何跟主redis关联起来呢?
答案藏在container的镜像里。这个镜像跟主redis的镜像不一样,找到其Dockerfile源码就会知道,follower的leader redis地址默认是redis-leader:6379
。所以,如果你在配置主redis服务的时候,用了一个别的名字,而又没有同步修正从redis的启动配置,就会导致从redis启动出错。
同理,后面要启动的guestbook php服务也是在代码里把默认的主从redis服务的地址与我们教程里的地址保持一致,从而能正常关联起来。
让我们来启动下这个服务,并且看下它是否已经与主redis连接上。
# apply yaml文件,启动了service与deployment
> kubectl apply -f redis-follower.yaml
service/redis-follower created
deployment.apps/redis-follower created
# 让我们来看下deployment的logs,这个logs比较多,也有点意思。大概分为四段,我们逐步注释一下。
> kubectl logs deployment/redis-follower
# 第一段
# 我们这个deployment只有两个pods,但是却找到了3个pods,并且第一个pod竟然是pod/redis-leader-dd4b55897-b7hxf
# 这个pod其实是我昨天启动的、上文提到的redis-leader
Found 3 pods, using pod/redis-leader-dd4b55897-b7hxf
# 第二段,redis-leader这个pod的log,可以看到是昨天启动的,并且已经Ready to accept connections
1:C 14 Nov 2023 12:56:34.373 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1:C 14 Nov 2023 12:56:34.373 # Redis version=6.0.5, bits=64, commit=00000000, modified=0, pid=1, just started
1:C 14 Nov 2023 12:56:34.373 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
1:M 14 Nov 2023 12:56:34.374 * Running mode=standalone, port=6379.
1:M 14 Nov 2023 12:56:34.374 # Server initialized
1:M 14 Nov 2023 12:56:34.374 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
1:M 14 Nov 2023 12:56:34.374 * Ready to accept connections
# 第三段 启动follower pod1的日志,从最后一句看,应该是与redis-leader连接成功了。
1:M 15 Nov 2023 03:47:12.475 * Replica 10.244.0.33:6379 asks for synchronization
1:M 15 Nov 2023 03:47:12.476 * Full resync requested by replica 10.244.0.33:6379
1:M 15 Nov 2023 03:47:12.476 * Replication backlog created, my new replication IDs are '96738f8c4e4bb780134a0e69bbffa25bd27606b1' and '0000000000000000000000000000000000000000'
1:M 15 Nov 2023 03:47:12.476 * Starting BGSAVE for SYNC with target: disk
1:M 15 Nov 2023 03:47:12.478 * Background saving started by pid 20
20:C 15 Nov 2023 03:47:12.483 * DB saved on disk
20:C 15 Nov 2023 03:47:12.483 * RDB: 0 MB of memory used by copy-on-write
1:M 15 Nov 2023 03:47:12.560 * Background saving terminated with success
1:M 15 Nov 2023 03:47:12.560 * Synchronization with replica 10.244.0.33:6379 succeeded
# 第四段 启动follower pod2的日志。
1:M 15 Nov 2023 03:47:16.356 * Replica 10.244.0.34:6379 asks for synchronization
1:M 15 Nov 2023 03:47:16.356 * Full resync requested by replica 10.244.0.34:6379
1:M 15 Nov 2023 03:47:16.356 * Starting BGSAVE for SYNC with target: disk
1:M 15 Nov 2023 03:47:16.357 * Background saving started by pid 21
21:C 15 Nov 2023 03:47:16.359 * DB saved on disk
21:C 15 Nov 2023 03:47:16.359 * RDB: 0 MB of memory used by copy-on-write
1:M 15 Nov 2023 03:47:16.384 * Background saving terminated with success
1:M 15 Nov 2023 03:47:16.385 * Synchronization with replica 10.244.0.34:6379 succeeded
logs里看到的redis-leader,应该就是deployment.spec.selector
写得太泛了导致的,至少应该再加一个role: follower
。 但是总之,从redis已经启动成功了。让我们继续后续的步骤。
步骤三:启动guestbook
同样创建个yaml文件,frontend.yaml
:
apiVersion: v1
kind: Service
metadata:
name: frontend
labels:
app: guestbook
tier: frontend
spec:
type: LoadBalancer
ports:
- port: 80
selector:
app: guestbook
tier: frontend
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
replicas: 3
selector:
matchLabels:
app: guestbook
tier: frontend
template:
metadata:
labels:
app: guestbook
tier: frontend
spec:
containers:
- name: php-redis
# 源码在https://github.com/GoogleCloudPlatform/kubernetes-engine-samples/blob/main/guestbook/php-redis/guestbook.php
image: gcr.io/google_samples/gb-frontend:v5
env:
- name: GET_HOSTS_FROM
value: "dns"
ports:
- containerPort: 80
启动这个服务:
> kubectl apply -f frontend.yaml
service/frontend created
deployment.apps/frontend created
# ......等待服务启动完成之后,因为osx下的docker是一个单独的虚拟机,所以还需要通过minikube把服务再转发出来。
> minikube service frontend
到此,整个服务就算搭建完成了。我们来试试服务伸缩实例,另起一个终端:
# 先来试试增加实例
> kubectl scale deployment/frontend --replicas=5
deployment.apps/frontend scaled
> kubectl get pods -l tier=frontend
NAME READY STATUS RESTARTS AGE
frontend-64b97d49f5-4926t 1/1 Running 0 28m
frontend-64b97d49f5-9mf5d 1/1 Running 0 32s
frontend-64b97d49f5-lln6t 1/1 Running 0 32s
frontend-64b97d49f5-th7xc 1/1 Running 0 32s
frontend-64b97d49f5-tx945 1/1 Running 0 28m
# 再来试试减少实例
> kubectl scale deployment/frontend --replicas=2
deployment.apps/frontend scaled
> kubectl get pods -l tier=frontend
NAME READY STATUS RESTARTS AGE
frontend-64b97d49f5-4926t 1/1 Running 0 29m
frontend-64b97d49f5-tx945 1/1 Running 0 29m
一切都是那么的丝滑~ 真是应了同事说过的一句话,大意思是:
k8s下,把服务当牲口养!
到此,也只能算是摸到了k8s的门槛,还有很多东西需要学。再有新的实践,再来分享吧~