Kubernetes架构师:基于世界500强的k8s实战课程2020年最新 K8S实战基础篇:一文带你深入了解K8S实战部署SpringBoot项目
Kubernetes架构师:基于世界500强的k8s实战课程2020年最新 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"]
#使用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 .
1
我们可以看到生成image的过程,通过docker images 查看镜像
生成一个 docker-demo:latest的image镜像。
注意:我们部署的是集群,要想K8S集群中都能拉到这个镜像,那我们有以下两种方式:
方法一:我们把这个docker-demo:latest上传到远端仓库,这个仓库可以是我们自己的,或者是像我一样注册一个阿里云的账号,上传到阿里云自己的容器镜像服务仓库,如下图:
具体步骤:
1.1 docker登陆阿里云容器镜像服务,需要输入密码
$ docker login –username=24k不怕(写自己的用户名) registry.cn-hangzhou.aliyuncs.com
1
1.2 在阿里云上创建命名空间:例:cuixhao-docker-demo
1.3 镜像打标签
$ docker tag docker-demo:latest registry.cn-hangzhou.aliyuncs.com/cuixhao-docker-demo/docker-demo:latest
1
1.4 push到阿里云
$ docker push registry.cn-hangzhou.aliyuncs.com/cuixhao-docker-demo/docker-demo:latest
1
1.5 删除掉docker-demo:latest
$ docker rmi docker-demo:latest
1
方法二 把刚才创建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
1
键入以下内容
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
1
查看启动结果:
$ kubectl get pod -o wide
1
我们可以看到启动结果,两个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
1
2
我们可以看到,接口返回了各自container的hostname,说明我们的服务是部署启动成功了,访问80端口是不通的,有兴趣的老铁可以试一下,因为80是我们对外的端口,所以用container ip是访问不通的。
b. 在集群内部访问,我们如何做到负载均衡?
有老铁可能会考虑一个问题,K8S集群中的pod有可能销毁或者重启,每次重启之后的ip不能保证一致,那以上访问方式肯定是不可采用的。想法很对,我们想访问一个固定的地址,不管pod如何重启,ip如何变化,我只访问这一个ip,这岂不美哉?那我们能不能做到呢?且看如下骚操作:
$ kubectl get svc
1
通过以上命令,我们找到了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
1
2
答案是肯定的!,它给我们做了负载均衡!
c. 在集群外部我们如何访问这两个服务并且负载均衡?
集群内访问服务,负载均衡都已经做好了。有的老铁会问:集群外服想访问集群内的服务,该如何做呢?别急,还没完!
3.3.3 引入Ingress
3.3.3.1 Ingress简介
我们传统的集群负载均衡做法是在一台机器上安装Nginx,把我们的服务配置在Nginx上,外部直接访问你Nginx,它帮我们做负载,做限流,但是今天我们玩了K8S,就不能在用这种方法了,我们大胆的想一下,我们把所有的东西都在K8S做了,岂不美哉!想法很好,”好事者”已经替我们想到了,并且替我们做到了。
kubernetes ingress 文档
我来简单介绍一下:
如图:在K8S中,Ingress 提供 controller接口,由各个负载均衡厂家实现,传统Nginx是配置在nginx.conf 中,在K8S中,我们只需要配置Ingress 资源yaml就可以,听起来是不是方便多了,我们可以像管理deployment,service,pod一样管理Ingress
3.3.3.2 Ingress 安装
我们使用 Nginx Ingress Controller 来一波骚操作:
编写 ingress-nginx.yaml
$ vi ingress-nginx.yaml
1
键入以下内容:
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
1
接下来,我们在master节点执行安装ingress操作
$ kubectl apply -f ingress-nginx.yaml
1
安装过程有点儿慢,因为有个镜像比较难拉取:quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.26.1,建议执行 docker pull 先拉取到本地,上传到阿里云,然后在各个节点从阿里云拉取,然后在打tag的骚操作,都是有经验的程序员,你们知道我在说什么!
3.3.3.3 Ingress 配置启动
编写Ingress yaml资源:
$ vi nginx-ingress.yaml
1
键入以下内容:
#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
1
3.3.3.4 验证
因为test.test.com是不存在的域名,我们都是有经验的开发人员,很自然的想到去修改本地host : 添加 ip test.test.com,ip为K8S节点中设置的别名为ingress的ip地址
浏览器访问:
完美!
3.3.3.4 自动扩缩
使用如下命令,可以然服务实现自动扩缩:
$ kubectl autoscale ingress-docker-docker-deployment.yaml –min=2 –max=5 –cpu-percent=80
1
还有很多自动扩缩的规则,老铁们自己探讨!
4. 总结
K8S实战还有很多玩法,我今天只是讲了最简单的服务部署,在不同的生产环境中,需求也是不一样的,比如说:一个简单的web应用,有mysql数据库,有redis,有springboot应用,都要在K8S中实践,这又是另一种部署方法,但万变不离其宗,核心都是要深入了解K8S网络,只有网络打通了,各个组件才会畅通无阻的运行。有兴趣的老铁可以关注一波,一起Hello World!
滴石it网-Java学习中高级和架构师教程_Java企业级开发项目实战下载 » Kubernetes架构师:基于世界500强的k8s实战课程2020年最新 K8S实战基础篇:一文带你深入了解K8S实战部署SpringBoot项目