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:
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:
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