K8S实战基础篇:一文带你深入了解K8S实战部署SpringBoot项目
K8S实战基础篇:一文带你深入了解K8S实战部署SpringBoot项目1.前言2.简介2.1.为什么写这篇文章2.2.需求描述2.3.需求分析3. 部署实战3.1 环境准备3.2 image准备3.3 部署2个实例3.3.1 编写yaml文件3.3.2 启动3.3.3 引入Ingress3.3.3.1 Ingress简介3.3.3.2 Ingress 安装3.3.3.3 Ingress
K8S实战基础篇:一文带你深入了解K8S实战部署SpringBoot项目
1.前言
云原生可以说是当下互联网行业最火爆的概念和技术,云原生从字面意思上来看可以分成云和原生两个部分。
云是和本地相对的,传统的应用必须跑在本地服务器上,现在流行的应用都跑在云端,云包含了IaaS,、PaaS和SaaS。
原生就是土生土长的意思,我们在开始设计应用的时候就考虑到应用将来是运行云环境里面的,要充分利用云资源的优点,比如️云服务的弹性和分布式优势。
聊到云原生,避不开的就是容器技术,而docker作为最流行的容器技术,已经经过很多年的线上实战。今天我们不深入聊云原生,docker这些技术概念,今天我们聊一聊时下最火的容器编排技术:K8S-实战部署SpringBoot项目。
2.简介
2.1.为什么写这篇文章
前言中提到云原生、docker、K8S,我是18年第一次docker,也是在18年接触K8S,相对这门技术来说,我接触的时候已经有些晚了,因为在之后的面试中,已经感受到这些技术在大厂已经用的很成熟了,之前都在小公司,并不了解这些技术是什么,干什么用,加上国内这方面的资料又比较少,学起来是相当吃力。而到大厂之后,发现这些技术无处不在,并且基础设施建设已经很完备,一键部署云端的骚操作,让开发只需要关心业务而无需关心安装部署等繁琐的工作。祸兮福之所倚;福兮祸之所伏,大厂的技术设施完备的同时,另一方面也消弱了你去了解基础设施背后的技术原理能力。正是认识到这一点,今天才写这篇文章,为迷途中的孩子找到回家的路。废话不多,撸起袖子,干就完了!
这里没有任何马后炮套话,只有粗暴的干货。写大家看得懂、用得着、赚得到的文章是唯一宗旨!
2.2.需求描述
我有一个简单的Springboot项目,想部署在K8S集群中,能够实现扩缩容,负载均衡,同时我有一个互联网域名,我想把这个域名绑定在这个服务上,能够在有网络的地方访问。
2.3.需求分析
这个需求我想在很多刚开始接触docker,k8s等技术的老铁身上都会遇到过,真正实现起来,并不是那么容易,听我一一道来:
- image—Springboot项目一般是以jar包的形式跑在像centos等服务器上,运行
nohup java -jar xxx.jar &
命令就能启动起来。但是在k8s中,运行起来的的并不是jar,而是image
,因此我们需要把jar打包成image; - 自动扩缩—最基础的image有了,接下来就要考虑的是自动扩缩:顾名思义,比如说就是在服务访问量大的时候,我可以添加实例,来减少每个服务实例的压力,在访问量小的时候,我可以删除一部分实例,来实现资源的高效利用。
- 负载均衡—当我们的实例越来越多,我并不希望把所有的请求都落在一个实例上,如若不然,我们自动扩缩也就没了意义,传统方法我们可以用Nginx等实现负载均衡,待会来看看K8S能做些什么
- 域名绑定—这个就没什么好说的了。
3. 部署实战
3.1 环境准备
工欲善其事,必先利其器:
- Springboot jar包
- K8S集群环境
K8S集群环境部署我就不在这里展开讲了,我们准备一个最简单的Springboot项目,里面只有一个接口,访问localhost:8088
,返回服务器的hostname
,当整个部署工作完成之后,我们通过域名访问这个接口,返回的应该是不同的container
的hostname
,那我们的任务就完成了。
@GetMapping("/")
public String sayHello() throws UnknownHostException {
String hostname = "Unknown";
InetAddress address = InetAddress.getLocalHost();
hostname = address.getHostName();
return hostname;
}
3.2 image准备
我们都知道,所有image的生成都离不开Dockerfile
技术,我们有了一个jar包,要利用Dockerfile技术生成一个image。废话不多,上代码:
#使用jdk8作为基础镜像
FROM java:8
#指定作者
MAINTAINER ***
#暴漏容器的8088端口
#EXPOSE 8088
#将复制指定的docker-demo-0.0.1-SNAPSHOT.jar为容器中的job.jar,相当于拷贝到容器中取了个别名
ADD docker-demo-0.0.1-SNAPSHOT.jar /job.jar
#创建一个新的容器并在新的容器中运行命令
RUN bash -c 'touch /job.jar'
#设置时区
ENV TZ=PRC
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
#相当于在容器中用cmd命令执行jar包 指定外部配置文件
ENTRYPOINT ["java","-jar","/job.jar"]
Dockerfile文件里面有注释,具体的每一行代码什么意思我就不展开多讲了,这不是今天的重点,接下来,我们把docker-demo-0.0.1-SNAPSHOT.jar
,Dockerfile
文件放在同一个目录,上传到K8S的master 节点上,在目录内执行如下命令生成images
$ docker build .
我们可以看到生成image的过程,通过docker images
查看镜像
生成一个 docker-demo:latest的image镜像。
注意:我们部署的是集群,要想K8S集群中都能拉到这个镜像,那我们有以下两种方式:
- 方法一:我们把这个docker-demo:latest上传到远端仓库,这个仓库可以是我们自己的,或者是像我一样注册一个阿里云的账号,上传到阿里云自己的容器镜像服务仓库,如下图:
具体步骤
:
1.1 docker登陆阿里云容器镜像服务,需要输入密码
$ docker login --username=24k不怕(写自己的用户名) registry.cn-hangzhou.aliyuncs.com
1.2 在阿里云上创建命名空间:例:cuixhao-docker-demo
1.3 镜像打标签
$ docker tag docker-demo:latest registry.cn-hangzhou.aliyuncs.com/cuixhao-docker-demo/docker-demo:latest
1.4 push到阿里云
$ docker push registry.cn-hangzhou.aliyuncs.com/cuixhao-docker-demo/docker-demo:latest
1.5 删除掉docker-demo:latest
$ docker rmi docker-demo:latest
- 方法二 把刚才创建image的过程,在集群中每一台节点上都执行一遍,保证集群中每一台都有这个镜像。我采用的是二者的结合:先在master上把镜像生成,上传到阿里云,然后在另外的节点上,通过
docker pull registry.cn-hangzhou.aliyuncs.com/cuixhao-docker-demo/docker-demo:latest
命令,从阿里云上拉到本地,然后在通过docer tag registry.cn-hangzhou.aliyuncs.com/cuixhao-docker-demo/docker-demo:latest docker-demo:latest
命令打标签,然后删掉拉取到的镜像:docker rmi registry.cn-hangzhou.aliyuncs.com/cuixhao-docker-demo/docker-demo:latest
,
为什么这么做?因为我阿里云建的命名空间中的image都是私有,K8S拉取image的时候是需要集群中都配置ca证书的,如果设置为公开则不存在这个问题。所以我用docker-demo:latest这个镜像,直接用本地的,部署的时候不用再去阿里云拉取。
3.3 部署2个实例
3.3.1 编写yaml文件
基础镜像准备好了,那我们就开始部署吧。我们知道,k8s有deployment
,service
等概念,这里不详细讲,简单描述一下:deployment(命名空间),管理pod集群,service
,管理pod中的服务。我们在master 节点编辑一个 ingress-docker-docker-deployment.yaml
文件
$ vi ingress-docker-docker-deployment.yaml
键入以下内容
apiVersion: apps/v1
kind: Deployment
metadata:
name: ingress-docker-demo-deployment
labels:
app: ingress-docker-demo
spec:
replicas: 2
selector:
matchLabels:
app: ingress-docker-demo
template:
metadata:
labels:
app: ingress-docker-demo
spec:
containers:
- name: docker-demo
image: docker-demo
imagePullPolicy: Never
ports:
- containerPort: 8088
---
apiVersion: v1
kind: Service
metadata:
name: ingress-docker-demo-service
spec:
ports:
- port: 80
protocol: TCP
targetPort: 8088
selector:
app: ingress-docker-demo
由yaml文件内容我们可以读出:我们创建了一个 名字我为ingress-docker-demo-deployment的deployment,里面有2个 docker-demo 的pod (replicas: 2
),和一个名字为ingress-docker-demo-service的service,管理名字为ingress-docker-demo的pod服务,目标端口8088,即我们的springboot服务所用端口,对外提供80端口。
3.3.2 启动
在master执行如下命令启动deployment 和service:
$ kubectl apply -f ingress-docker-docker-deployment.yaml
查看启动结果:
$ kubectl get pod -o wide
我们可以看到启动结果,两个container
分别在 worker01,worker02这两个节点上,可以看到,K8S集群给我们分配了两个IP:192.168.14.11
,192.168.221.73
。我们思考以下三个问题:
a. 在集群中,我们通过以上访问这两个服务,能访问通吗,是通过8088端口还是80端口?
我们不妨尝试一下,在集群中任何一个节点执行如下命令:
$ curl 192.168.14.11:8088
$ curl 192.168.221.73:8088
我们可以看到,接口返回了各自container的hostname,说明我们的服务是部署启动成功了,访问80端口是不通的,有兴趣的老铁可以试一下,因为80是我们对外的端口,所以用container ip是访问不通的。
b. 在集群内部访问,我们如何做到负载均衡?
有老铁可能会考虑一个问题,K8S集群中的pod有可能销毁或者重启,每次重启之后的ip不能保证一致,那以上访问方式肯定是不可采用的。想法很对,我们想访问一个固定的地址,不管pod如何重启,ip如何变化,我只访问这一个ip,这岂不美哉?那我们能不能做到呢?且看如下骚操作:
$ kubectl get svc
通过以上命令,我们找到了ingress-docker-docker-deployment.yaml
中定义的名字为 ingress-docker-demo-service
的service,它有一个 CLUSTER-IP
,PORT
为80,那我们根据K8S中的service的作用,做一个大胆的猜测:我们是不是可以固定的通过 10.103.19.71
(省略默认80端口)或者 10.103.19.71:80
来永久访问这两个服务呢?
$ curl 10.103.19.71
$ curl 10.103.19.71:80
答案是肯定的!,它给我们做了负载均衡!
c. 在集群外部我们如何访问这两个服务并且负载均衡?
集群内访问服务,负载均衡都已经做好了。有的老铁会问:集群外服想访问集群内的服务,该如何做呢?别急,还没完!
3.3.3 引入Ingress
我们传统的集群负载均衡做法是在一台机器上安装Nginx,把我们的服务配置在Nginx上,外部直接访问你Nginx,它帮我们做负载,做限流,但是今天我们玩了K8S,就不能在用这种方法了,我们大胆的想一下,我们把所有的东西都在K8S做了,岂不美哉!想法很好,"好事者"已经替我们想到了,并且替我们做到了。
kubernetes ingress 文档
我来简单介绍一下:
如图:在K8S中,Ingress 提供 controller接口,由各个负载均衡厂家实现,传统Nginx是配置在nginx.conf 中,在K8S中,我们只需要配置Ingress 资源yaml就可以,听起来是不是方便多了,我们可以像管理deployment
,service
,pod
一样管理Ingress
我们使用 Nginx Ingress Controller
来一波骚操作:
编写 ingress-nginx.yaml
$ vi ingress-nginx.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
---
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
hostNetwork: true
nodeSelector:
name: ingress
kubernetes.io/os: linux
containers:
- name: nginx-ingress-controller
image: quay.io/kubernetes-ingress-controller/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
---
这个文件并不是我胡编乱造自己写的,是"好事者"帮我们做好了,我只是稍作修改:设置网络模式为hostNetwork:true
,我希望我在集群中一台机器上开一个80端口,用这台机器作为负载均衡入口,因此:nodeSelector: name: ingress
。这是节点选择器配置参数,设置这个,ingress服务会在节点名字为ingress的机器上部署。
接下来我们在集群中的除master节点之外的一个机器上执行下个命令:给这台hostname为worker01-kubeadm-k8s的机器取个别名ingress
$ kubectl label node worker01-kubeadm-k8s name=ingress
接下来,我们在master节点执行安装ingress操作
$ kubectl apply -f ingress-nginx.yaml
安装过程有点儿慢,因为有个镜像比较难拉取:quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.26.1
,建议执行 docker pull 先拉取到本地,上传到阿里云,然后在各个节点从阿里云拉取,然后在打tag的骚操作,都是有经验的程序员,你们知道我在说什么!
编写Ingress yaml资源:
$ vi nginx-ingress.yaml
键入以下内容:
#ingress
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx-ingress
spec:
rules:
- host: test.test.com
http:
paths:
- path: /
backend:
serviceName: ingress-docker-demo-service
servicePort: 80
文件定义了一种Ingress的资源,配置host为:test.test.com
,代理K8S中名字为ingress-docker-demo-service
的 Service, Service端口为80.看起来是不是和Nginx配置有点儿类似?
最后一步:启动
$ kubectl apply -f nginx-ingress.yaml
因为test.test.com是不存在的域名,我们都是有经验的开发人员,很自然的想到去修改本地host : 添加 ip test.test.com
,ip为K8S节点中设置的别名为ingress的ip地址
浏览器访问:
完美!
使用如下命令,可以然服务实现自动扩缩:
$ kubectl autoscale ingress-docker-docker-deployment.yaml --min=2 --max=5 --cpu-percent=80
还有很多自动扩缩的规则,老铁们自己探讨!
4. 总结
K8S实战还有很多玩法,我今天只是讲了最简单的服务部署,在不同的生产环境中,需求也是不一样的,比如说:一个简单的web应用,有mysql数据库,有redis,有springboot应用,都要在K8S中实践,这又是另一种部署方法,但万变不离其宗,核心都是要深入了解K8S网络
,只有网络打通了,各个组件才会畅通无阻的运行。有兴趣的老铁可以关注一波,一起Hello World!
转载至https://blog.csdn.net/cuixhao110/article/details/105734878
更多推荐
所有评论(0)