preface

Recently, I have been learning how to use Jenkins to integrate and deploy the SpringCloud microservice and the front-end NeXT. JS project with Ali Cloud K8S automation. Now I would like to share it with you, if there is anything wrong with the article, I hope you can comment and correct.

Enterprise CI/CD process

object-oriented

  • Proficient in using Vue or React
  • Proficient in using SpringCloud microservices
  • Proficient in using Docker containers
  • Familiar with Jenkins automation operation and maintenance tools
  • Skilled in K8S (Deployment, Service, Ingress)

The preparatory work

1. Buy the ACK cluster of Aliyun (or build it by yourself)

What I bought is Alibaba Cloud ACK managed version to create cluster address

Note: There is no charge for creating an ACK cluster, but the NAT gateway, SLB load balancer, ECS server, etc

2. Install GITLAB

Here I wrote an article before Aliyun ECS build GITLAB, install GITLAB address

3. Install Jenkins

Here I wrote an article before Ali cloud ECS build Jenkins, install Jenkins address

4. Install Docker

It is convenient to install using YUM recommended by the official website. The installation is as follows:

sudo yum install -y yum-utils \
  device-mapper-persistent-data \
  lvm2

sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo

sudo yum install docker-ce docker-ce-cli containerd.io

System architecture

Next. Js front-end development

Next, js project:

Service: React + NeXT. Js Service Port: 3000, K8S: Deployment+Server+Ingress Pod Name: Demo-WebApp

Dockerfile

From node:12 # Set the work path. WORKDIR WORKDIR /usr/ SRC /app # install COPY package*. Json./ RUN NPM install # COPY CMD [" NPM ", "start"]

SpringCloud microservice development

Service discovery: SpringCloud Eureka

Microservice name: demo-eureka-server microservice port: 8761, K8S: Deployment+Service+Ingress (add Ingress because you want to check the Service registration through the URL) Pod name: demo-eureka-server

2, Service configuration: SpringCloud Config

Microservice name: demo-config-server microservice port: 8888, K8S: Deployment+Service Pod name: demo-config-server

Service Authentication and Authorization: SpringSecurity + OAuth2

Microservice port: 8901, K8S: Deployment+ service Pod name: demo-auth-service

Service gateway: SpringCloud Zuul

Microservice port: 5555, K8S: Deployment+Service+Ingress (Service Gateway is the only entry point for all services, so Ingress should be configured) Pod name: demo-auth-service

5. Wrote Dockerfile and K8S YAML

For convenience, the above micro service name and POD name are set to the same name, the specific service development process here is temporarily ignored, later time to write an article to build enterprise micro service, the related micro service directory structure is as follows:

Note: Dockerfile and K8S YAML files are much the same between different services. Let’s take Eureka as an example:

Eureka Dockerfile:

FROM openjdk:8-jdk-alpine
MAINTAINER "zhangwei"<[email protected]>
RUN mkdir -p /usr/local/configsvr
ARG JAR_FILE
ADD ${JAR_FILE} /usr/local/configsvr/app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/usr/local/configsvr/app.jar"]
EXPOSE 8888

Eureka K8S Yaml:

apiVersion: extensions/v1beta1 kind: Ingress metadata: annotations: nginx.ingress.kubernetes.io/configuration-snippet: | rewrite ^/eureka/css/(.*)$ /eureka/eureka/css/$1 redirect; rewrite ^/eureka/js/(.*)$ /eureka/eureka/js/$1 redirect; nginx.ingress.kubernetes.io/force-ssl-redirect: 'true' nginx.ingress.kubernetes.io/rewrite-target: /$2 nginx.ingress.kubernetes.io/service-weight: '' generation: 4 name: <podname>-ingress namespace: default spec: rules: - host: baidu.com http: paths: - backend: serviceName: <podname>-svc servicePort: 8761 path: /eureka(/|$)(.*) pathType: ImplementationSpecific --- apiVersion: v1 kind: Service metadata: name: <podname>-svc namespace: default spec: externalTrafficPolicy: Local ports: - nodePort: 31061 port: 8761 protocol: TCP targetPort: 8761 selector: app: <podname> sessionAffinity: None type: NodePort --- apiVersion: apps/v1 kind: Deployment metadata: annotations: deployment.kubernetes.io/revision: '1' generation: 1 labels: app: <podname> name: <podname> namespace: default spec: progressDeadlineSeconds: 600 replicas: 1 revisionHistoryLimit: 10 selector: matchLabels: app: <podname> strategy: rollingUpdate: maxSurge: 25% maxUnavailable: 25% type: RollingUpdate template: metadata: labels: app: <podname> spec: containers: - env: - name: LANG value: C.UTF-8 - name: JAVA_HOME value: /usr/lib/ JVM/java-1.8-OpenJDK image: < imageName > imagePullPolicy: ifNotPresent Name: <podname> ports: -containerPort: 8761 protocol: TCP resources: requests: cpu: 250m memory: 512Mi terminationMessagePath: /dev/termination-log terminationMessagePolicy: File dnsPolicy: ClusterFirst restartPolicy: Always schedulerName: default-scheduler securityContext: {} terminationGracePeriodSeconds: 30

The bootstrap.yml and application.yml files are different from each other, for example:

Demo – auth – service, the demo – zuul – server configuration bootstrap. Add yml file SpringCloud Config address:

Spring: Application: Name: AuthService Cloud: Config: Enabled: True # Config http://demo-config-server-svc:8888

Add the Eureka address to the application.yml file, and add the Eureka address:

eureka: instance: preferIpAddress: true hostname: demo-eureka-server-svc client: register-with-eureka: True Fetch-Registry: True Service-URL: # Eureka Server Name DefaultZone: http://demo-eureka-server-svc:8761/eureka/

Git workflow

According to Git workflow, we can generally divide it into development environment (Develop), test environment (Release), pre-production environment (UAT) and production environment (Prop), and the corresponding branches are: Dev, test, release, master, so different phases of the submitted code branch is different, and dockerfile, Jenkins pipline, K8S YAML files are also different, this should be noted.

Jenkins DevOps CI/CD

1. View specification

Jenkins can add test views, pre-production views, and production views according to the Git workflow, as follows:

2. Create pipeline tasks



We first create the pipeline task and write a Pipline Script script to create the CI/CD pipeline step

3. Wrote the front-end pipline script

Start by writing environment variables

Environment {GIT_REPOSITORY=" address of front-end code repository "k8s_YAML ="k8s YAML file directory" DOCKER_USERNAME="docker warehouse user name "DOCKER_PWD="docker warehouse password" ALIYUN_DOCKER_HOST = 'Aliyun_docker_namespace =' Aliyun_docker_namespace = 'Aliyun_docker namespace' Aliyun_Docker_Repository_Name =" The name of the repository under the AliCloud Docker repository namespace "}

Step 1: Clone the code

Stage ("Clone") {steps {echo "1.Clone stage "// DeleteDir () // test branch, jenkins-gitlab-ssh-hash is the SSH key, Git branch: 'test', credentialsId: 'jenkins-gitlab-ssh-hash', url: ${GIT_REPOSITORY} ${GIT_REPOSITORY} ${GIT_REPOSITORY} ${GIT_REPOSITORY}" 'git rev-parse --short HEAD').trim() // Assemb to full address DOCKER_REPOSITORY = "${ALIYUN_DOCKER_HOST}/${ALIYUN_DOCKER_NAMESPACE}/${ALIYUN_DOCKER_REPOSITORY_NAME}" DOCKER_REPOSITORY_TAG = "${DOCKER_REPOSITORY}:${GIT_TAG}" } } }

Step 2: Test the code

stage("Test") {
    steps {
        echo "2.Test Stage"
    }
}

Step 3: Make a Docker image

stage("Build") {
    steps {
        echo "3.Build Docker Image Stage"
        sh "docker build -t ${DOCKER_REPOSITORY_TAG} -f docker/Dockerfile ."
    }
}

Step 4: Push the Docker image

Stage ("Push") {steps {echo "4.Push Docker Image stage "// Push Docker Image Image, "Docker login --username=${DOCKER_USERNAME} --password=${DOCKER_USERNAME} --password=${DOCKER_PWD}" ${ALIYUN_DOCKER_HOST}" // start to push the image to Aricloud docker repository sh "// delete the image sh generated by Jenkins docker images | grep seaurl | awk '{print $3}' | xargs docker rmi -f ''' } }

Step 5: K8S deploys the Docker image

stage("Deploy") { steps { echo "5. "// Replace <imagename> and <podname> sh in k8s YAML "sed - I 's#<imagename>#${DOCKER_REPOSITORY_TAG}#g; S #< Podname >#${Pod_name}# G '${K8S_YAML}" // Apply - F ${K8S_YAML}"}

The syntax of sed is as good as you can get. I used a # delimiter instead of a/delimiter because the delimited character contains a /, so it can no longer be used.

First, let’s look at the front-end K8S YAML file

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod-http01
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/force-ssl-redirect: 'true'
    nginx.ingress.kubernetes.io/service-weight: ''
  generation: 3
  name: <podname>-ingress
  namespace: default
spec:
  rules:
    - host: baidu.com
      http:
        paths:
          - backend:
              serviceName: <podname>-svc
              servicePort: 3000
            path: /
            pathType: ImplementationSpecific
  tls:
    - hosts:
        - baidu.com
      secretName: <podname>-ingress
---
apiVersion: v1
kind: Service
metadata:
  name: <podname>-svc
  namespace: default
spec:
  ports:
    - port: 3000
      protocol: TCP
      targetPort: 3000
  selector:
    app: <podname>
  sessionAffinity: None
  type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: '1'
  generation: 1
  labels:
    app: <podname>
  name: <podname>
  namespace: default
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: <podname>
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app:<podname>
    spec:
      containers:
        - image: <imagename>
          imagePullPolicy: IfNotPresent
          name: <podname>
          resources:
            requests:
              cpu: 250m
              memory: 512Mi

The YAML file above contains: Configured in the deployment, service and ingress, ingress TLS, so you can use HTTPS to access the domain name, and configure nginx. Ingress. Kubernetes. IO/force – SSL – redirect: and < imageName > are replaced by sed with the real name and address.

Complete Pipline Script script:

Pipeline {agent any environment {GIT_REPOSITORY=" k8s_YAML "="k8s YAML file directory" DOCKER_USERNAME="docker warehouse user name" DOCKER_PWD="docker password "ALIYUN_DOCKER_HOST = 'Aliyun_docker_namespace' =" Aliyun_docker_namespace" Aliyun_Docker_Repository_Name =" Repository_Name under the AliCloud Docker Repository namespace "} stages {stage("Clone") {steps {echo "1 Git branch: 'test', credentialsId: git branch: 'test'; 'Jenkins-gitlab-ssh-hash ', url: "${GIT_REPOSITORY}" script {${GIT_REPOSITORY}"; true, script: 'git rev-parse --short HEAD').trim() // Assemb to full address DOCKER_REPOSITORY = "${ALIYUN_DOCKER_HOST}/${ALIYUN_DOCKER_NAMESPACE}/${ALIYUN_DOCKER_REPOSITORY_NAME}" DOCKER_REPOSITORY_TAG = "${DOCKER_REPOSITORY}:${GIT_TAG}" } } } stage("Test") { steps { echo "2.Test Stage" } } stage("Build") { steps { echo "3.Build Docker Image Stage" sh "docker build -t ${DOCKER_REPOSITORY_TAG} -f docker/Dockerfile ." } } stage("Push") { Steps {echo "4.Push Docker Image Stage" // Push Docker Image Stage, "Docker login --username=${DOCKER_USERNAME} --password=${DOCKER_USERNAME} --password=${DOCKER_PWD}" ${ALIYUN_DOCKER_HOST}" // start to push the image to Aricloud docker repository sh "// delete the image sh generated by Jenkins docker images | grep seaurl | awk '{print $3}' | xargs docker rmi -f ''' } } stage("Deploy") { steps { echo "5. "// Replace <imagename> and <podname> sh in k8s YAML "sed - I 's#<imagename>#${DOCKER_REPOSITORY_TAG}#g; S #< Podname >#${Pod_name}# G '${K8S_YAML}" // Apply K8S YAML sh "Kubectl Apply - F ${K8S_YAML}"}}}}

Click Build Now, as shown below:

4. Write pipline script for micro-service

Start by writing environment variables

Environment {GIT_REPOSITORY=" code repository address "MODULE_NAME="maven module name" POD_NAME="k8s pod name" K8S_YAML = "${MODULE_NAME} / SRC/main/k8s/eurekasvr yaml" DOCKER_USERNAME = "docker warehouse user name" DOCKER_PWD = "docker warehouse password" ALIYUN_DOCKER_HOST = Aliyun_docker_namespace =" Aliyun_docker warehouse namespace" Aliyun_Docker_Repository_Name =" The name of the repository under the AliCloud Docker repository namespace "}

Step 1: Clone the code

Stage ("Clone") {steps {echo "1.Clone stage "// DeleteDir () // test branch, jenkins-gitlab-ssh-hash is the SSH key, Git branch: 'test', credentialsId: 'jenkins-gitlab-ssh-hash', url: ${GIT_REPOSITORY} ${GIT_REPOSITORY} ${GIT_REPOSITORY} ${GIT_REPOSITORY}" 'git rev-parse --short HEAD').trim() // Assemb to full address DOCKER_REPOSITORY = "${ALIYUN_DOCKER_HOST}/${ALIYUN_DOCKER_NAMESPACE}/${ALIYUN_DOCKER_REPOSITORY_NAME}" DOCKER_REPOSITORY_TAG = "${DOCKER_REPOSITORY}:${GIT_TAG}" } } }

Step 2: Test the code

stage("Test") {
    steps {
        echo "2.Test Stage"
    }
}

Step 3: Make a Docker image

stage("Build") { steps { echo "3.Build Server" sh "mvn -e -U -pl ${MODULE_NAME} -am clean package -Dmaven.test.skip=true  dockerfile:build -Ddockerfile.tag=${GIT_TAG} -Ddockerfile.repository=${DOCKER_REPOSITORY}" } }

-pl is used to specify the name of the module. Then we use dockerfile-maven-plugin in our pom.xml, as shown below:

<build> <plugins> <plugin> <groupId>com.spotify</groupId> <artifactId>dockerfile-maven-plugin</artifactId> The < version > 1.4.10 < / version > < configuration > <! - specifies the directory with dockerfile - > < dockerfile > / SRC/main/docker dockerfile < / dockerfile > < buildArgs > <! --> <JAR_FILE>target/${project.build.finalName}. Jar </JAR_FILE> </buildArgs> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>

Step 4: Push the Docker image

Stage ("Push") {steps {echo "4.Push Docker Image stage "// Push Docker Image Image, "Docker login --username=${DOCKER_USERNAME} --password=${DOCKER_USERNAME} --password=${DOCKER_PWD}" ${ALIYUN_DOCKER_HOST}" // start to push the image to Aricloud docker repository sh "// delete the image sh generated by Jenkins docker images | grep seaurl | awk '{print $3}' | xargs docker rmi -f ''' } }

Step 5: K8S deploys the Docker image

stage("Deploy") { steps { echo "5. "// Replace <imagename> and <podname> sh in k8s YAML "sed - I 's#<imagename>#${DOCKER_REPOSITORY_TAG}#g; S #< Podname >#${Pod_name}# G '${K8S_YAML}" // Apply - F ${K8S_YAML}"}

The syntax of sed is as good as you can get. I used a # delimiter instead of a/delimiter because the delimited character contains a /, so it can no longer be used.

First, let’s take a look at the Eureka K8S YAML file

apiVersion: extensions/v1beta1 kind: Ingress metadata: annotations: nginx.ingress.kubernetes.io/configuration-snippet: | rewrite ^/eureka/css/(.*)$ /eureka/eureka/css/$1 redirect; rewrite ^/eureka/js/(.*)$ /eureka/eureka/js/$1 redirect; nginx.ingress.kubernetes.io/force-ssl-redirect: 'true' nginx.ingress.kubernetes.io/rewrite-target: /$2 nginx.ingress.kubernetes.io/service-weight: '' generation: 4 name: <podname>-ingress namespace: default spec: rules: - host: baidu.com http: paths: - backend: serviceName: <podname>-svc servicePort: 8761 path: /eureka(/|$)(.*) pathType: ImplementationSpecific --- apiVersion: v1 kind: Service metadata: name: <podname>-svc namespace: default spec: externalTrafficPolicy: Local ports: - nodePort: 31061 port: 8761 protocol: TCP targetPort: 8761 selector: app: <podname> sessionAffinity: None type: NodePort --- apiVersion: apps/v1 kind: Deployment metadata: annotations: deployment.kubernetes.io/revision: '1' generation: 1 labels: app: <podname> name: <podname> namespace: default spec: progressDeadlineSeconds: 600 replicas: 1 revisionHistoryLimit: 10 selector: matchLabels: app: <podname> strategy: rollingUpdate: maxSurge: 25% maxUnavailable: 25% type: RollingUpdate template: metadata: labels: app: <podname> spec: containers: - env: - name: LANG value: C.UTF-8 - name: JAVA_HOME value: /usr/lib/ JVM/java-1.8-OpenJDK image: < imageName > imagePullPolicy: ifNotPresent Name: <podname> ports: -containerPort: 8761 protocol: TCP resources: requests: cpu: 250m memory: 512Mi terminationMessagePath: /dev/termination-log terminationMessagePolicy: File dnsPolicy: ClusterFirst restartPolicy: Always schedulerName: default-scheduler securityContext: {} terminationGracePeriodSeconds: 30

The YAML file above contains: Configured in the deployment, service and ingress, ingress TLS, so you can use HTTPS to access the domain name, and configure nginx. Ingress. Kubernetes. IO/force – SSL – redirect: and < imageName > are replaced by sed with the real name and address.

Complete Pipline Script script:

Pipeline {agent any environment {GIT_REPOSITORY=" code repository address "MODULE_NAME="maven module name" POD_NAME="k8s pod name" K8S_YAML = "${MODULE_NAME} / SRC/main/k8s/eurekasvr yaml" DOCKER_USERNAME = "docker warehouse user name" DOCKER_PWD = "docker warehouse password" ALIYUN_DOCKER_HOST = Aliyun_docker_namespace =" Aliyun_docker warehouse namespace" Aliyun_Docker_Repository_Name =" Repository_Name under the AliCloud Docker Repository namespace "} stages {stage("Clone") {steps {echo "1 deleteDir() git branch: 'test',credentialsId: '1297dda3-e592-4e70-8fb0-087a26c08db0', url: ${GIT_REPOSITORY} ${GIT_REPOSITORY} ${GIT_REPOSITORY} ${GIT_REPOSITORY} ${GIT_REPOSITORY} 'git describe --tags --always').trim() // fetch git commit hash as docker repository tag GIT_TAG = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim() DOCKER_REPOSITORY = "${ALIYUN_DOCKER_HOST}/${ALIYUN_DOCKER_NAMESPACE}/${ALIYUN_DOCKER_REPOSITORY_NAME} DOCKER_REPOSITORY_TAG = "${DOCKER_REPOSITORY}:${GIT_TAG}" } } } stage("Test") { steps { echo "2.Test Stage" } } stage("Build") { steps { echo "3.Build Server" sh "mvn -e -U -pl ${MODULE_NAME} -am clean package -Dmaven.test.skip=true  dockerfile:build -Ddockerfile.tag=${GIT_TAG} -Ddockerfile.repository=${DOCKER_REPOSITORY}" } } stage("Push") { steps { Echo "4.Push Docker Image Stage" // Push Docker Image Stage, "Docker login --username=${DOCKER_USERNAME} --password=${DOCKER_USERNAME} --password=${DOCKER_PWD}" ${ALIYUN_DOCKER_HOST}" // start to push the image to Aricloud docker repository sh "// delete the image sh generated by Jenkins docker images | grep seaurl | awk '{print $3}' | xargs docker rmi -f ''' } } stage("Deploy") { steps { echo "5. "Sh "sed - I 's#< dockerRepository >#${DOCKER_REPOSITORY_TAG}#g; s#<podname>#${POD_NAME}#g' ${K8S_YAML}" sh "kubectl apply -f ${K8S_YAML}" } } } }

Click Build Now, as shown below:

Check to see if K8S was successful

You can do it by command

kubectl get deploy
kubectl get pod
kubectl get svc
kubectl get ingress

You can also view the POD log to analyze its success or failure:

kubectl logs podname

Visit Eureka

PostMan accesses the microservice interface address

You can access the external Zuul address via the PostMan interface to see if it can be authenticated:

The browser accesses the Web page address

To see if the deployment was successful, check the deployment address of next.js:

conclusion

1. There are a variety of Jenkins Pipline scripts and Dockerfile scripts on the Internet, so you can find one that conforms to the standard. Deployment will be created if it is not, otherwise it will be updated. -pl and dockerfile-maven-plugin are used by Jenkins to compile the maven module of the microservice. 5. If you want to use k8s Ingress, you need to set HTTPS for the microservice. And HTTP automatically jumps to HTTPS

reference

A Kubernetes app is configured using JenkinsPipeline to configure -ci-cd-on-Kubernetes-with-Jenkins Spring-k8s. This app is configured using JenkinsPipeline to configure -ci-cd-on-Kubernetes-with-Jenkins Spring-k8s A microservice infrastructure based on OAuth2.0 Uniform Authentification Authorization applies for a free HTTPS certificate using the cert-manager Spring Boot uses spring.profiles. Active = @spring.active @ to switch configuration files between different environments https://kuboard.cn/learning/k8s-practice/ocp/eureka-server.html#%E6%9F%A5%E7%9C%8B%E9%83%A8%E7%BD%B2%E7%BB%93%E6%9E%9C Kubernetes deploys a simple example of the microservice Spring Cloud k8s-nginx-ingress Eureka secondary path forwarding problem