(project practice) how to combine the pipeline of k8s and pipeline and upgrade the image through k8s interface?

Posted by writer on Tue, 01 Feb 2022 10:54:43 +0100

###Foreword
Now the CICD of this unit is in chaos, and then I have a whim and want to transform it, so I made a simple pipeline with pipeline. Here are some introductions about it

Write a simple pipeline

It's probably such a process. To put it simply, it's: pull code - compile - create image - push image - deploy to k8s. The following pipeline is based on this main line and added according to the situation

pipeline {
	agent { label 'pdc&&jdk8' }
	environment {
		git_addr = "Code warehouse address"
		git_auth = "Authentication when pulling code ID"
		pom_dir = "pom Directory location of the file (relative path)"
		server_name = "service name"
		namespace_name = "Namespace where the service is located"
		img_domain = "Mirror Address "
		img_addr = "${img_domain}/cloudt-safe/${server_name}"
// 		cluster_name = "cluster name"
	}
	stages {
		stage('Clear dir') {
			steps {
				deleteDir()
			}
		}
		stage('Pull server code and ops code') {
			parallel {
				stage('Pull server code') {
					steps {
						script {
							checkout(
								[
									$class: 'GitSCM',
									branches: [[name: '${Branch}']],
									userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_addr}"]]
								]
							)
						}
					}
				}
				stage('Pull ops code') {
					steps {
						script {
							checkout(
								[
									$class: 'GitSCM',
									branches: [[name: 'pipeline-0.0.1']], //Branch of the pulled build script
									doGenerateSubmoduleConfigurations: false,
									extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'DEPLOYJAVA']], //Deploy Java: store the code in this directory
									userRemoteConfigs: [[credentialsId: 'chenf-o', url: 'Warehouse address of build script']]
								]
							)
						}
					}
				}
			}
		}
		stage('Set Env') {
			steps {
				script {
					date_time = sh(script: "date +%Y%m%d%H%M", returnStdout: true).trim()
					git_cm_id = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
					whole_img_addr = "${img_addr}:${date_time}_${git_cm_id}"
				}
			}
		}
		stage('Complie Code') {
			steps {
				script {
					withMaven(maven: 'maven_latest_linux') {
					    sh "mvn -U package -am -amd -P${env_name} -pl ${pom_dir}"
					}
				}
			}
		}
		stage('Build image') {
			steps {
				script {
					dir("${env.WORKSPACE}/${pom_dir}") {
						sh """
							echo 'FROM Base image address' > Dockerfile  //Since I have optimized the image here, I can only specify a basic image address, which will be described in detail later
						"""
						withCredentials([usernamePassword(credentialsId: 'faabc5e8-9587-4679-8c7e-54713ab5cd51', passwordVariable: 'img_pwd', usernameVariable: 'img_user')]) {
							sh """
								docker login -u ${img_user} -p ${img_pwd} ${img_domain}
								docker build -t ${img_addr}:${date_time}_${git_cm_id} .
								docker push ${whole_img_addr}
							"""
						}
					}
				}
			}
		}
		stage('Deploy img to K8S') {
			steps {
				script {
					dir('DEPLOYJAVA/deploy') {
					    //Execute build script
						sh """
							/usr/local/python3/bin/python3 deploy.py -n ${server_name} -s ${namespace_name} -i ${whole_img_addr} -c ${cluster_name}
						"""
					}
				}
			}
			// After making a judgment, if the above script fails to execute, the image in the above stage will be deleted
			post {
				failure {
					sh "docker rmi -f ${whole_img_addr}"
				}
			}
		}
		stage('Clear somethings') {
			steps {
				script {
				    // Delete image
					sh "docker rmi -f ${whole_img_addr}"
				}
			}
			post {
				success {
				    // The current directory will be deleted if the execution phase is successful
					deleteDir()
				}
			}
		}
	}
}

Optimize the construction of images

There is a command in the above pipeline to generate Dockerfile, and many optimizations have been made here. Although my Dockerfile has written a FROM, a series of operations will be performed after that. Let's compare the Dockerfile that has not been optimized

FROM Base image address
RUN mkdir xxxxx
COPY *.jar /usr/app/app.jar
ENTRYPOINT java -jar app.jar

Optimized

FROM Base image address

The optimized Dockerfile is finished in this line..... Here is a brief introduction to ONBUILD
ONBUILD can be understood in this way. For example, the image we use here is an image based on java language. This image has two parts: one is the basic image A containing JDK, and the other is the image B containing jar package. The relationship is A before B, that is, B depends on A.
Assuming a complete CICD scenario based on Java, we need to pull the code, compile, create the image, push the image and update the pod. In the process of creating the image, we need to COPY the compiled product jar package into the basic image, which leads us to write a Dockerfile to COPY the jar package, just like the following:

FROM jdk base image 
COPY xxx.jar /usr/bin/app.jar
ENTRYPOINT java -jar app.jar

It looks good. It's basically solved in three lines, but it can be solved in one line. Why use three lines?

FROM jdk base image 
ONBUILD COPY target/*.jar /usr/bin/app.jar
CMD ["/start.sh"]

Make an image. For example, the image name is java service: jdk1 8. During mirroring, the following after ONBUILD will not be executed during local mirroring, but will be executed during the next reference

FROM java-service:jdk1.8

Only this line is needed, and it looks more concise, and the pipeline looks very standardized. In this way, every java service can use this line of Dockerfile.

Use credentials

Sometimes, when using docker for push image, authentication is required. If we write directly in pipeline, it is not very safe, so we have to desensitize it. In this way, we need to use credentials. Adding credentials is also very simple. Because we only save our user name and password, we can use the credentials of Username with password, As shown below

For example, the code for pulling git warehouse needs to be used, and then a credential is added here, which corresponds to the following paragraph in pipeline:

stage('Pull server code') {
	steps {
		script {
			checkout(
				[
					$class: 'GitSCM',
					branches: [[name: '${Branch}']],
					userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_addr}"]]
				]
			)
		}
	}
}

The variable ${git_auth} here is the ID set when adding credentials. If the ID is not set, an ID will be randomly generated

Then, when docker push es, authentication and credentials need to be added. The adding method is the same as above, but we can generate one with Pipeline Syntax as follows: click Pipeline Syntax

Select withCredentials: Bind credentials to variables

Then bind with the previously added credentials. Here, select the type: Username and password (separated)

Set the variable name of user name and password, and then select the credentials just added

Click generate, which is the following paragraph in the pipeline above:

withCredentials([usernamePassword(credentialsId: 'faabc5e8-9587-4679-8c7e-54713ab5cd51', passwordVariable: 'img_pwd', usernameVariable: 'img_user')]) {
	sh """
		docker login -u ${img_user} -p ${img_pwd} ${img_domain}
		docker build -t ${img_addr}:${date_time}_${git_cm_id} .
		docker push ${whole_img_addr}
	"""
}

credentialsId: this ID is a randomly generated ID

Execute script to update image

Here is a small script written in python to call the interface of kubernetes and complete a patch operation. Let's first look at the directory structure of this script

Core code: deploy py
Core file: config Yaml stores kubeconfig files for authentication with kubernetes

Next, post deploy For the script content of Py, please refer to the following:

import os
import argparse
from kubernetes import client, config

class deployServer:
    def __init__(self, kubeconfig):
        self.kubeconfig = kubeconfig
        config.kube_config.load_kube_config(config_file=self.kubeconfig)
        self._AppsV1Api = client.AppsV1Api()
        self._CoreV1Api = client.CoreV1Api()
        self._ExtensionsV1beta1Api = client.ExtensionsV1beta1Api()

    def deploy_deploy(self, deploy_namespace, deploy_name, deploy_img=None, deploy_which=1):
        try:
            old_deploy = self._AppsV1Api.read_namespaced_deployment(
                name=deploy_name,
                namespace=deploy_namespace,
            )
            old_deploy_container = old_deploy.spec.template.spec.containers
            pod_num = len(old_deploy_container)
            if deploy_which == 1:
                pod_name = old_deploy_container[0].name
                old_img = old_deploy_container[0].image
                print("Get information about the previous version\n")
                print("current Deployment have {} individual pod, by: {}\n".format(pod_num, pod_name))
                print("The image address of the previous version is: {}\n".format(old_img))
                print("The image address of this build is: {}\n".format(deploy_img))
                print("Replacing the mirror address of the current service....\n")
                old_deploy_container[deploy_which - 1].image = deploy_img
            else:
                print("Only one mirror address can be replaced")
                exit(-1)
            new_deploy = self._AppsV1Api.patch_namespaced_deployment(
                name=deploy_name,
                namespace=deploy_namespace,
                body=old_deploy
            )
            print("The mirror address has been replaced\n")
            return new_deploy
        except Exception as e:
            print(e)

def run():
    parser = argparse.ArgumentParser()
    parser.add_argument('-n', '--name', help="Build service name")
    parser.add_argument('-s', '--namespace', help="The namespace in which the service to be built resides")
    parser.add_argument('-i', '--img', help="The image address of this build")
    parser.add_argument('-c', '--cluster',
                        help="rancher The name of the cluster in which the current service is located")
    args = parser.parse_args()
    if not os.path.exists('../config/' + args.cluster):
        print("The current cluster name is not set or the name is incorrect: {}".format(args.cluster), 'red')
        exit(-1)
    else:
        kubeconfig_file = '../config/' + args.cluster + '/' + 'config.yaml'
        if os.path.exists(kubeconfig_file):
            cli = deployServer(kubeconfig_file)
            cli.deploy_deploy(
                deploy_namespace=args.namespace,
                deploy_name=args.name,
                deploy_img=args.img
            )
        else:
            print("Current cluster kubeconfig Does not exist, please configure at{}Lower config.yaml.(be careful: config.yaml The name is written dead and does not need to be changed to)".format(args.cluster),
                  'red')
            exit(-1)

if __name__ == '__main__':
    run()

The writing is relatively simple and there is nothing difficult to understand. The key points are:

new_deploy = self._AppsV1Api.patch_namespaced_deployment(
                name=deploy_name,
                namespace=deploy_namespace,
                body=old_deploy
            )

This sentence is to perform a patch operation to patch the contents of the replaced new image address.
Then it's execution.

other

One thing to note here is that an exception capture is added to the pipeline, as shown below:

post {
	success {
	    // If the above phase is successfully executed, the current directory will be deleted
		deleteDir()
	}
}

The writing method of exception capture of life pipeline and script pipeline is different. The declarative writing method is judged by post. It is relatively simple. You can refer to the official documents
In addition, there is another place where parallel execution is used, which pulls the code of the service and the code of the build script at the same time, which can improve the speed of executing the whole pipeline, as shown below:

parallel {
	stage('Pull server code') {
		steps {
			script {
				checkout(
					[
						$class: 'GitSCM',
						branches: [[name: '${Branch}']],
						userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_addr}"]]
					]
				)
			}
		}
	}
	stage('Pull ops code') {
		steps {
			script {
				checkout(
					[
						$class: 'GitSCM',
						branches: [[name: 'pipeline-0.0.1']], //Branch of the pulled build script
						doGenerateSubmoduleConfigurations: false,
						extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'DEPLOYJAVA']], //Deploy Java: store the code in this directory
						userRemoteConfigs: [[credentialsId: 'chenf-o', url: 'Warehouse address of build script']]
					]
				)
			}
		}
	}
}

Well, that's the case. A simple pipeline is completed. If you want to use the pipeline to complete CICD quickly, you can refer to this article.

Write at the end

After reading, you can leave a message below to discuss what you don't understand
Thank you for watching.
If you think the article is helpful to you, remember to pay attention to me and give me some praise and support!

Author: fei
Link: https://juejin.cn/post/6922388073456074766

last

I also collected a core knowledge of Java interview to cope with the interview. I can give it to my readers for free by taking this opportunity:

catalog:

Core knowledge points of Java interview

There are 30 topics in total, which is enough for readers to cope with the interview and save time for friends to search for information everywhere and sort it out by themselves!

Core knowledge points of Java interview

In this case, a simple pipeline is completed. If you want to use the pipeline to complete CICD quickly, you can refer to this article.

Write at the end

After reading, you can leave a message below to discuss what you don't understand
Thank you for watching.
If you think the article is helpful to you, remember to pay attention to me and give me some praise and support!

Author: fei
Link: https://juejin.cn/post/6922388073456074766

last

I also collected a core knowledge of Java interview to cope with the interview. I can give it to my readers for free by taking this opportunity:

catalog:

[external chain picture transferring... (img-jl9adgv7-162355251792)]

Core knowledge points of Java interview

There are 30 topics in total, which is enough for readers to cope with the interview and save time for friends to search for information everywhere and sort it out by themselves!

[external chain picture transferring... (img-bkwssq2t-162355251793)]

Core knowledge points of Java interview

Data collection method: after likes[ Stamp interview data ]You can get it for free!

Topics: Java Interview Programmer