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项目

常见问题FAQ

发表回复

开通VIP 享更多特权,建议使用QQ登录