Kubernetes持续交付实践一

在上两个章节,分别讲述了基础环境的安装以及环境的测试环节,我们也可以理解一个持续集成的环境,那么在此正式进入我们的最终目标,实现整个大框架,将我们的具有生产能力的应用部署到私有Kubernetes集群中来,那么具体搭建Kuberentes环境就不在此赘述,因为机器的内存较小的原因,原本打算开三个节点的Kubernetes,发现实际情况不允许,Gitlab和Harbor服务器台耗费内存,那么带来的另一个原因就是,我们的应用程序多活可能也比较吃力,这里根据实际情况来确定某应用的副本吧;

接下来就正式进入实践环节,对于SpringCloud项目还有一个特点,任何应用向eureka注册,默认是通过主机名来注册的,这不符合我们的需要,因为在Pod内部是无法访问通过主机名来实现Pod访问的,所以此时我们需要修改我们是SpringCloud项目,将其注册方式修改为IP注册,直接利用IP向eureka注册,具体配置方式,在下面会讲到;

eureka

在此主要是说我们的eureka这个项目如何实现持续集成的,首先我们需要知道一个问题,对于SpringCloud项目来讲,对于eureka是个特殊的应用,它是一个无状态应用,但是因为其独有的特性,也就是说其他的任何微服务都需要与其进行注册,所以在此,我们需要使用两种控制器,一种的StateFulSet、Deployment,对于我们的eureka则使用StateFulSet,来让其在Pod重建的时候保证其PodName不发生变化;

Kubernetes预配置

在正式部署之前,我们需要做一些预配置,比如Namespace、ServiceAccount资源也最好事先去创建;

# 创建一个springcloud的Namespace,作为该项目的命名空间

[root@node1 ~]# cat springcloud_ns.yaml

apiVersion: v1

kind: Namespace

metadata:

  name: springcloud

[root@node1 ~]# kubectl apply -f springcloud_ns.yaml

# 创建一个ServiceAccount,并使其能够管理springcloud这个命名空间

[root@node1 ~]# cat springcloud_sa.yaml

apiVersion: v1

kind: ServiceAccount

metadata:

  name: spring

  namespace: springcloud

---

apiVersion: rbac.authorization.k8s.io/v1

kind: ClusterRoleBinding

metadata:

  name: springcloud-clusterrolebinding

  namespace: springcloud

roleRef:

  apiGroup: rbac.authorization.k8s.io

  kind: ClusterRole

  name: cluster-admin

subjects:

  - kind: ServiceAccount

    name: spring

    namespace: springcloud

[root@node1 ~]# kubectl apply -f springcloud_sa.yaml

# 获取该ServiceAccount的token

[root@node1 ~]# kubectl describe secrets -n springcloud $(kubectl describe sa -n springcloud spring|grep '^Tokens'|awk '{print $2}')|awk '/^token/{print $2}'

...

eureka配置更新

因为eureka之间也是会相互注册的,所以对于eureka也需要修改其注册地址为现在所有可用的eureka的地址,在后面我们将eureka的副本设为两个,所以这里注册地址也需要写两个,然后还需要修改其配置为IP注册,这样才能让各应用之间互相连通;

# 将eureka的代码clone下来

[root@node4 ~]# git clone http://192.168.1.64/SpringCloud/eureka.git

[root@node4 ~]# cd eureka/

[root@node4 eureka]# cat src/main/resources/application.yml 

spring:

  application:

    name: eureka

server:

  port: 6210

eureka:

  instance:

    prefer-ip-address: true  # 修改为IP注册

    hostname: eureka

  client:

    registerWithEureka: true #true表示向注册中心注册自己。

    fetchRegistry: false

    serviceUrl:  #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务

      defaultZone: http://eureka-0.eureka-svc.springcloud.svc.cluster.local:6210/eureka/,http://eureka-1.eureka-svc.springcloud.svc.cluster.local:6210/eureka/  # 配置两个eureka的注册地址

# 提交到主干

[root@node4 eureka]# git add *

[root@node4 eureka]# git commit -m 'edit config'

[root@node4 eureka]# git push origin master

此处修改了配置,别忘记重新打包上传到我们的Harbor

修改Docker配置

我们的Kubernetes主机之上的docker daemon也需要修改一些配置,主要是因为我们的Harbor仓库的非安全的,所以需要在每台Kubernetes的工作节点配置如下配置;

注意:每台Kuberentes集群的工作节点都需要配置

[root@node1 ~]# cat > /etc/docker/daemon.json 

{

    registry-mirrors: [https://owcyje1z.mirror.aliyuncs.com],

    insecure-registries:[http://192.168.1.64:8181]

}

EOF

[root@node1 ~]# systemctl daemon-reload

[root@node1 ~]# systemctl restart docker

Harbor认证

因为我们的Harbor仓库并不是公开访问的,那么此时,我们的Kubernetes在pull镜像的时候有会碰到用户认证的问题,所以我们需要实现配置一个认证的Secret;

首先,我们在Kubernetes主节点上登录一遍,生成registry认证文件

[root@node1 ~]# docker login 192.168.1.64:8181 

[root@node1 ~]# cat /root/.docker/config.json 

{

        auths: {

                192.168.1.64:8181: {

                        auth: YWRtaW46Y2FpY2hhbmdlbg==

                }

        },

        HttpHeaders: {

                User-Agent: Docker-Client/19.03.8 (linux)

        }

}

# 得到该文件的base64密文

[root@node1 ~]# base64 -w 0 /root/.docker/config.json 

ewoJImF1dGhzIjogewoJCSIxOTIuMTY4LjEuNjQ6ODE4MSI6IHsKCQkJImF1dGgiOiAiWVdSdGFXNDZZMkZwWTJoaGJtZGxiZz09IgoJCX0KCX0sCgkiSHR0cEhlYWRlcnMiOiB7CgkJIlVzZXItQWdlbnQiOiAiRG9ja2VyLUNsaWVudC8xOS4wMy44IChsaW51eCkiCgl9Cn0=

# 基于认证文件来创建Secret

[root@node1 ~]# cat registry_auth.yaml

apiVersion: v1

kind: Secret

metadata:

  name: registry

  namespace: springcloud

data:

  .dockerconfigjson: ewoJImF1dGhzIjogewoJCSIxOTIuMTY4LjEuNjQ6ODE4MSI6IHsKCQkJImF1dGgiOiAiWVdSdGFXNDZZMkZwWTJoaGJtZGxiZz09IgoJCX0KCX0sCgkiSHR0cEhlYWRlcnMiOiB7CgkJIlVzZXItQWdlbnQiOiAiRG9ja2VyLUNsaWVudC8xOS4wMy44IChsaW51eCkiCgl9Cn0=  # 上面的base64的密文

type: kubernetes.io/dockerconfigjson

配置清单

我们需要预先将eureka的配置清单给做出来,并且将其上传到我们的eureka仓库,具体如下;

# 将eureka的代码clone下来

[root@node4 ~]# git clone http://192.168.1.64/SpringCloud/eureka.git

[root@node4 ~]# cd eureka/

[root@node4 eureka]# cat > eureka.yaml 

apiVersion: apps/v1

kind: StatefulSet

metadata:

  name: eureka

  labels:

    app: eureka

  annotations:

    author: cce

  namespace: springcloud

spec:

  serviceName: eureka-svc

  replicas: 2

  template:

    metadata:

      name: eureka-pod

      namespace: springcloud

      labels:

        app: eureka

    spec:

      imagePullSecrets:

        - name: registry

      containers:

        - name: eureka-container

          image: 192.168.1.64:8181/springcloud/eureka:4

          imagePullPolicy: IfNotPresent

          livenessProbe:  # 配置健康状态检查

            initialDelaySeconds: 3

            successThreshold: 1

            timeoutSeconds: 10

            failureThreshold: 3

            httpGet:

              port: 6210

              scheme: HTTP

      restartPolicy: Always

  selector:

    matchLabels:

      app: eureka

---

apiVersion: v1

kind: Service

metadata:

  name: eureka-svc

  namespace: springcloud

spec:

  selector:

    app: eureka

  ports:

    - name: http

      protocol: TCP

      port: 6210

      targetPort: 6210

  clusterIP: None   # 无头Service

---

apiVersion: extensions/v1beta1

kind: Ingress

metadata:

  name: springcloud-ingress

  namespace: springcloud

spec:

  rules:

    - host: eureka.springcloud.com # 记得将其加入hosts文件 192.168.1.61 eureka.springcloud.com

      http:

        paths:

          - backend:

              serviceName: eureka-svc

              servicePort: 6210

            path: /

EOF

[root@node4 eureka]# git add *

[root@node4 eureka]# git commit -m 'add kubernetes config'

[root@node4 eureka]# git push origin master

# 第一次部署的时候先手动部署,但是别忘记了上面做了很多修改,此时需要重新构建下Jenkins,重新生成新的镜像

[root@node1 ~]# kubectl apply -f eureka.yaml

[root@node1 ~]# kubectl get all -n springcloud 

NAME           READY   STATUS    RESTARTS   AGE

pod/eureka-0   1/1     Running   0          3m10s

pod/eureka-1   1/1     Running   0          2m56s

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE

service/eureka-svc   ClusterIP   None         <none>        6210/TCP   m10s

NAME                      READY   AGE

statefulset.apps/eureka   2/2     3m10s

# 测试连通性

[root@node1 ~]# kubectl exec -it -n springcloud eureka-0 sh

/ # wget -S -O /dev/null -q http://eureka-0.eureka-svc.springcloud.svc.cluster.local:6210  # 第一个eureka正常

  HTTP/1.1 200 

  Content-Type: text/html;charset=UTF-8

  Content-Language: en-US

  Transfer-Encoding: chunked

  Date: Wed, 22 Apr 2020 04:28:12 GMT

  Connection: close

/ # wget -S -O /dev/null -q http://eureka-1.eureka-svc.springcloud.svc.cluster.local:6210 第一个eureka访问第二个eureka正常

  HTTP/1.1 200 

  Content-Type: text/html;charset=UTF-8

  Content-Language: en-US

  Transfer-Encoding: chunked

  Date: Wed, 22 Apr 2020 04:28:16 GMT

  Connection: close

测试访问

好了,现在我们的eureka已经手动部署完成,接下来就来测试下eureka是否能够正常访问,因为我们使用的是内部域名所以需要先添加hosts解析

添加hosts解析

企业实战|Kubernetes持续交付实践一

测试访问eureka是否部署成功

企业实战|Kubernetes持续交付实践一

部署脚本

此处并非进行最后的应用部署,只是测试行的利用Python来调通Kubernetes集群,并且直接使用Python来远程管理集群,为最后一步应用部署做基础;

# 找到上面创建的ServiceAccount的token

[root@node1 ~]# kubectl describe secrets -n springcloud $(kubectl describe sa -n springcloud spring|grep '^Tokens'|awk '{print $2}')|awk '/^token/{print $2}'

...

# 该token就可以作为我们管理这个springcloud命名空间的资源了,先测试下是否能够连通

企业实战|Kubernetes持续交付实践一

在原有的持续集成的脚本之上附加一项持续部署的到Kubernetes的代码,实现持续部署


#!/usr/bin/env python3

# -*- coding: utf-8 -*-

# @Time    : 2020/4/15 23:43

# @Author  : CaiChangEn

# @Email   : mail0426@163.com

# @Software: PyCharm

import docker, os, shutil, time, kubernetes

registry = '192.168.1.64:8181'

project = 'springcloud'

sitename = 'eureka'

webfile = 'eureka.jar'

sts_name = 'eureka'

sts_namespace = 'springcloud'

class Deploy(object):

    def __init__(self, rep, pro, name, file, sname, snamespace):

        '''

        :param rep: docker仓库地址(Harbor为例);

        :param pro: 仓库的项目名称(Harbor为例)

        :param name: 仓库的镜像名称;

        :param file: 对于SpringCloud项目编译成功后生成的jar包的名称;

        '''

        print('33[1;31mStep1 Start initialization33[0m')

        StartTime = time.time()

        self.KubernetesObj = kubernetes.client.Configuration()

        self.KubernetesObj.host = 'https://192.168.1.61:6443'

        self.KubernetesObj.verify_ssl = False

        self.KubernetesObj.api_key[

            'authorization'] = 'eyJhbGciOiJSUzI1NiIsImtpZCI6IlVxTXN4dGI2UXRlNHFoWmNuSDluRzFLQllKRHVrN2tUVE16TEU2N3lKMVkifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJzcHJpbmdjbG91ZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJzcHJpbmctdG9rZW4tbWh4enEiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoic3ByaW5nIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiZmUzZWNlM2UtNGY2Mi00ZThhLWEzZWYtY2M5NmFlNTVmMjFkIiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OnNwcmluZ2Nsb3VkOnNwcmluZyJ9.Qe-UOE9oT34VAlqeNIZuISznxn5nvnGcRvOsp5J7njcJgjXrGHkjGna-h9yBA4epQE6iJ1jgr80GboXNseMHZx8Ui6UuZXwR_GmRCuyziji1nDqRR2UqocQuTaXSKMGeHsa7F4L7gGytTcq9NlkeNAfGX6iTibD01bRjFCJGRxH9VGYKnng_Q89C1C_JmdLW3VLXOvecugd6xsP9CzktQjZB1CBZSuatehPHc3yjFzkKImP7d3W-y-OaYOK-5QSA5L4-IW10tJrlQC4ovwHVqKcVe1uLvWC3dSQkS5TMn3PELA2-r3De2J_h8xAcAgnFCI0gypJynssypmNpSa3nXg'

        self.KubernetesObj.api_key_prefix['authorization'] = 'Bearer'

        self.StsName = sname

        self.StsNamespace = snamespace

        self.DockerClientObj = docker.DockerClient()

        self.WorkSpace = os.getenv('WORKSPACE')

        self.BuildId = os.getenv('BUILD_ID')

        self.Context = os.path.join(self.WorkSpace, 'context')

        self.SrcDockerFile = os.path.join(self.WorkSpace, 'Dockerfile')

        self.DestDockerFile = os.path.join(self.Context, 'Dockerfile')

        self.WebSiteFile = os.path.join(self.WorkSpace, 'target', file)

        self.BuildTag = os.path.join(rep, pro, name) + ':' + self.BuildId

        self.Repository = os.path.join(rep, pro, name)

        print('33[1;32mStep1 Use Time %.3f33[0m' % float(time.time() - StartTime))

    def createEnv(self):

        '''

        该方法主要是创建一些初始环境,提供Dockerfile和需要构建的上下文

        '''

        print('33[1;31mStep2 create env33[0m')

        StartTime = time.time()

        os.mkdir(self.Context)

        shutil.move(self.WebSiteFile, self.Context)

        shutil.move(self.SrcDockerFile, self.Context)

        print('33[1;32mStep2 Use Time %.3f33[0m' % float(time.time() - StartTime))

        return self.buildImage()

    def deleteImage(self):

        '''

        该方法主要是删除镜像,如果正在构建的镜像已在本地存在,那么先删除

        '''

        try:

            self.DockerClientObj.images.remove(self.BuildTag)

            print('33[1;34mDeleted Image %s33[0m' % (self.BuildTag))

        except Exception:

            pass

    def buildImage(self):

        '''

        该方法正式开始构建镜像,并且给镜像指定对应的tag,默认的tag为jenkins的BUILD_ID

        '''

        print('33[1;31mStep3 Build Image33[0m')

        StartTime = time.time()

        self.deleteImage()

        ImageObj, BuildLog = self.DockerClientObj.images.build(path=self.Context, quiet=False,

                                                               dockerfile=self.DestDockerFile,

                                                               tag=self.BuildTag, rm=True,

                                                               forcerm=True)

        for Log in BuildLog:

            print(Log)

        print('33[1;32mStep3 Use Time %.3f33[0m' % float(time.time() - StartTime))

        return self.pushImage()

    def pushImage(self):

        '''

        该方法主要是将镜像上传到私有仓库,此案例主要是基于http协议的harbor仓库

        '''

        print('33[1;31mStep4 Push Image33[0m')

        StartTime = time.time()

        PushLog = self.DockerClientObj.images.push(repository=self.Repository, tag=self.BuildId,

                                                   auth_config={username: admin, password: caichangen},

                                                   decode=True)

        print(PushLog, end='')

        print('33[1;32mStep4 Use Time %.3f33[0m' % float(time.time() - StartTime))

        self.deleteImage()

        return self.deployKubernetes()

    def deployKubernetes(self):

        print('33[1;31mStep5 Deploy Kubernetes33[0m')

        StartTime = time.time()

        api_instance = kubernetes.client.AppsV1Api(kubernetes.client.ApiClient(self.KubernetesObj))

        sts_instance = api_instance.read_namespaced_stateful_set(name=self.StsName, namespace=self.StsNamespace)

        sts_instance.spec.template.spec.containers[<span class="code-snippet__number">0].image=self.BuildTag

        api_instance.patch_namespaced_stateful_set(name=self.StsName, namespace=self.StsNamespace,body=sts_instance)

        print('33[1;32mStep5 Deploy Kubernetes %.3fn部署成功33[0m' % float(time.time() - StartTime))

        return None

deploy = Deploy(registry, project, sitename, webfile, sts_name, sts_namespace)

deploy.createEnv()

正式部署

接下来就是正式的正式上线环节了,但是有一点别忘记了,镜像的tag是通过Jenkins的BUILDID来生成的;

企业实战|Kubernetes持续交付实践一

可以看到我们的两个eureka副本的镜像tag也变成了Jenkins的BUILDID

[root@node1 ~]# kubectl get pods -n springcloud -o json|jq .items[].spec.containers[].image

192.168.1.64:8181/springcloud/eureka:5

192.168.1.64:8181/springcloud/eureka:5

收官测试

接下来做最后一次测试,eureka的收官之测;

企业实战|Kubernetes持续交付实践一

查看创建的镜像

企业实战|Kubernetes持续交付实践一

可以看到,我们的上线流程的切换着来

[root@node1 ~]# kubectl get pods -n springcloud -o json|jq .items[].spec.containers[].image

192.168.1.64:8181/springcloud/eureka:5

192.168.1.64:8181/springcloud/eureka:5

[root@node1 ~]# kubectl get pods -n springcloud -o json|jq .items[].spec.containers[].image

192.168.1.64:8181/springcloud/eureka:5

192.168.1.64:8181/springcloud/eureka:6

[root@node1 ~]# kubectl get pods -n springcloud -o json|jq .items[].spec.containers[].image

192.168.1.64:8181/springcloud/eureka:5

192.168.1.64:8181/springcloud/eureka:6

[root@node1 ~]# kubectl get pods -n springcloud -o json|jq .items[].spec.containers[].image

192.168.1.64:8181/springcloud/eureka:5

192.168.1.64:8181/springcloud/eureka:6

[root@node1 ~]# kubectl get pods -n springcloud -o json|jq .items[].spec.containers[].image

192.168.1.64:8181/springcloud/eureka:5

192.168.1.64:8181/springcloud/eureka:6

[root@node1 ~]# kubectl get pods -n springcloud -o json|jq .items[].spec.containers[].image

192.168.1.64:8181/springcloud/eureka:5

192.168.1.64:8181/springcloud/eureka:6

[root@node1 ~]# kubectl get pods -n springcloud -o json|jq .items[].spec.containers[].image

192.168.1.64:8181/springcloud/eureka:5

192.168.1.64:8181/springcloud/eureka:6

[root@node1 ~]# kubectl get pods -n springcloud -o json|jq .items[].spec.containers[].image

192.168.1.64:8181/springcloud/eureka:6

192.168.1.64:8181/springcloud/eureka:6

至此,整个eureka的部署已经完成,那么在下一个环节将结束整个SpingCloud项目的持续交付的操作;

1. 本站所有资源来源于用户上传和网络,如有侵权请及时联系删除,本站不承担任何法律责任!
2. 分享目的仅供大家学习和研究,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的教程、源码等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
7. 如遇到加密压缩包,默认解压密码为"www.94zyw.com",如遇到无法解压的请联系管理员!
94资源网 » Kubernetes持续交付实践一