Jenkins file one line of code to deploy. NET program to K8S

What is Jenkins shared library

With the increase of microservices, pipeline files are required for each project. In this way, pipeline code redundancy is high, and pipeline functions become more and more complex.

jenkins can use Shared Lib to abstract some public pipelines into module code and reuse them among various project pipelines to reduce redundancy.

Shared library directory structure

Shared library root
|-- vars
    |-- test1.groovy
|-- src
    |-- test2.groovy
|-- resources

vars: Groovy script that depends on Jenkins running environment. The Groovy script is called a global variable.
src: the standard Java source directory structure in which Groovy scripts are called library classes.
resources: the directory allows the use of the libraryResource step from an external library to load relevant non Groovy files.

How to reference a shared library

#!/usr/bin/env groovy

// Reference shared libraries configured by default
@Library('demo-shared-library') _

// Reference the specified branch tag Shared library code for
@Library('demo-shared-library@1.0') _

// Reference multiple specified branches tag Shared library for
@Library('demo-shared-library@$Branch/Tag','demo-shared-library-test@$Branch/Tag') _

@Library('utils') import org.foo.Utilities

@Library('utils') import static org.foo.Utilities.*

Global variables under vars

/Global variables under vars must be all lowercase or camel cased
/If vars/*.groovy implements the call() method, the method will be executed by default when it is referenced directly

Implement one line of code to deploy. NET programs to K8S

Install Jenkins Master

# master
docker run --name jenkins-blueocean -u root --privileged -d -p 8080:8080 -p 50000:50000 -v D:/architecture/jenkins/data:/var/jenkins_home -v /var/run/docker.sock:/var/run/docker.sock jenkinsci/blueocean 

# Visit: http://ip:8080/
# jenkins password, check the container log: 7285ced325a24483bfdaab227415fdac
# Install recommended plug-ins

Install Jenkins Agent

 
Manage Jenkins -> Manage Nodes and Clouds -> New Node
  • Name: agent2/agent3
  • Labels: agentnode
  • Launch method: Launch agent by connecting it to the master

Build Agent Docker Image

# slave
# Dockerfile
FROM jenkins/inbound-agent:latest
USER root

RUN apt-get update
RUN apt-get -y install ca-certificates curl gnupg lsb-release
RUN curl -fsSL https://get.docker.com -o get-docker.sh
RUN sh get-docker.sh


RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
RUN install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
RUN kubectl version --client

ENV JMETER_VERSION=5.4.1
ENV JMETER_HOME=/jmeter/apache-jmeter-${JMETER_VERSION}
ENV JMETER_PATH=${JMETER_HOME}/bin:${PATH}
ENV PATH=${JMETER_HOME}/bin:${PATH}

RUN mkdir /jmeter
COPY apache-jmeter-${JMETER_VERSION}.tgz /jmeter
RUN cd /jmeter && tar xvf apache-jmeter-${JMETER_VERSION}.tgz #Please go to the official website to download the tgz files
RUN sed -i 's/#jmeter.save.saveservice.output_format=csv/jmeter.save.saveservice.output_format=xml/g'  /jmeter/apache-jmeter-5.4.1/bin/jmeter.properties

 

docker build -t chesterjenkinsagent .
docker tag chesterjenkinsagent:latest 192.168.43.95/jenkins/chesterjenkinsagent:v1
docker login --username=admin --password=123456  192.168.43.95
docker push 192.168.43.95/jenkins/chesterjenkinsagent:v1

Run the agent, and the key is obtained through jenkins's agent information

# agent4
docker run  -v /var/run/docker.sock:/var/run/docker.sock --name agent4  -d --init 192.168.43.95/jenkins/chesterjenkinsagent:v1 -url http://192.168.43.94:8080   1e84c896dbffc0c325587eedb6301ab0ae66d3f4b49c4628dbb05714e382d7a2 agent4

 

Add K8S credentials

  • Export the k8s cluster configuration file to ~ /. kube/config
  •  Mange Jenkins -> Manage Credentials -> Add Credentials -> Secret File
  • Select the exported kubeconfig and set the id to kubeconfig

Add Harbor credentials

  • Mange Jenkins -> Manage Credentials -> Add Credentials -> Username with password
  • Enter the user name and password of Harbor

Add Gitee credentials

  • Mange Jenkins -> Manage Credentials -> Add Credentials -> Username with password
  • Enter Gitee's username and password

Install plug-ins

Manage Jenkins -> Manage Plugins -> Search "Performance" -> install

Manage shared libraries

Manage Jenkins - > configure system - > global pipeline libraries, where the git address is the address of the shared library

Add the following code to the shared library

vars/run.groovy

#!/usr/bin/env groovy

def call(String nameSpaceName, String serviceName, String servicePath, String servicePort, String nodePort, Map envInfo) {
    def devBranch = envInfo['dev']
    def prodBranch = envInfo['prod']

    pipeline {
        agent {
            label 'agentnode'
        }

        environment {
            DEV_MY_KUBECONFIG = credentials('kubeconfig')
            PROD_MY_KUBECONFIG = credentials('kubeconfig')
            HARBOR = credentials('harbor')
        }

        stages {
            stage('Dev - GitPull') {
                steps {
                    deleteDir()
                    gitCheckOut devBranch, env.GIT_URL
                }
                post {
                    success {
                        script {
                            echo 'pull done'
                        }
                    }
                }
            }
            stage('Dev - DockerBuild') {
                steps {
                    dockerImageBuild serviceName, "${servicePath}Dockerfile"
                }
            }
            stage('Dev - DockerTag') {
                steps {
                    dockerTag serviceName, 'dev'
                }
            }
            stage('Dev - DockerLogin') {
                steps {
                    dockerLogin 'dev'
                }
            }
            stage('Dev - DockerPush') {
                steps {
                    dockerPush serviceName, 'dev'
                }
            }
            stage('Dev - GenerateHarborSecretYAML') {
                steps {
                    harborSecret nameSpaceName, serviceName, 'dev'
                }
            }
            stage('Dev - GenerateK8SYAML') {
                steps {
                    k8sGenerateYaml nameSpaceName, serviceName, servicePath, 'dev', servicePort, nodePort
                }
            }
            stage('Dev - DeployToK8S') {
                steps {
                    k8sDeploy servicePath, 'dev'
                }
            }
            stage('Dev - CheckDeployStatus') {
                steps {
                    k8sCheckDeployStatus nameSpaceName, serviceName, 'dev'
                }
            }
            stage('Dev - Jmeter Test') {
                steps {
                    jmeterTest servicePath
                }
            }

            stage('DeployToProd?') {
                steps {
                    input 'Deploy production?'
                }
            }

            stage('Prod - GitPull') {
                steps {
                    gitCheckOut prodBranch, env.GIT_URL
                }
            }
            stage('Prod - DockerBuild') {
                steps {
                    dockerImageBuild serviceName, "${servicePath}Dockerfile"
                }
            }
            stage('Prod - DockerTag') {
                steps {
                    dockerTag serviceName, 'prod'
                }
            }
            stage('Prod - DockerLogin') {
                steps {
                    dockerLogin 'prod'
                }
            }
            stage('Prod - DockerPush') {
                steps {
                    dockerPush serviceName, 'prod'
                }
            }
            stage('Prod - GenerateHarborSecretYAML') {
                steps {
                    harborSecret nameSpaceName, serviceName, 'prod'
                }
            }
            stage('Prod - GenerateK8SYAML') {
                steps {
                    k8sGenerateYaml nameSpaceName, serviceName, servicePath, 'prod', servicePort, nodePort
                }
            }
            stage('Prod - DeployToK8S') {
                steps {
                    k8sDeploy servicePath, 'prod'
                }
            }
            stage('Prod - CheckDeployStatus') {
                steps {
                    k8sCheckDeployStatus nameSpaceName, serviceName, 'prod'
                }
            }
        }
    }
}

 

vars/dockerImageBuild.groovy

#!/usr/bin/env groovy

def call(String serviceName, String dockerfilePath) {
    echo "serviceName:${serviceName} dockerfilePath:${dockerfilePath}"
    sh "docker build -t ${serviceName} -f  ${dockerfilePath} ."
}

 

vars/dockerLogin.groovy

#!/usr/bin/env groovy

def call(String envName) {
    sh 'docker login --username=$HARBOR_USR --password=$HARBOR_PSW  192.168.43.95'
}

 

vars/dockerPush.groovy

#!/usr/bin/env groovy

def call(String serviceName,String envName) {
    sh "docker push 192.168.43.95/dapr/${serviceName}:${envName}-${BUILD_NUMBER}"
}

 

vars/dockerTag.groovy

#!/usr/bin/env groovy

def call(String serviceName, String envName) {
    sh "docker tag ${serviceName}:latest 192.168.43.95/dapr/${serviceName}:${envName}-${BUILD_NUMBER}"
}

 

vars/gitCheckOut.groovy

#!/usr/bin/env groovy

def call(String branchName, String gitUrl) {
    echo "branchName:${branchName} gitUrl:${gitUrl}"
    checkout([$class: 'GitSCM', branches: [[name: branchName]], extensions: [], userRemoteConfigs: [[credentialsId: 'gitee', url: gitUrl]]])
}

 

vars/harborSecret.groovy

def call(String namespaceName, String serviceName, String envName) {
    dir('harborsecret') {
        checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [], userRemoteConfigs: [[credentialsId: 'gitee', url: 'https://gitee.com/chesterdotchen/jenkins-demo-secrets.git']]])
        sh """sed -i 's/{{ServiceName}}/${serviceName}/g'  secrets.yaml"""
        sh """sed -i 's/{{NameSpaceName}}/${namespaceName}/g'  secrets.yaml"""

        if (envName == 'dev') {
            sh("kubectl --kubeconfig  ${DEV_MY_KUBECONFIG} apply -f secrets.yaml")
        }
        if (envName == 'prod') {
            sh("kubectl --kubeconfig  ${PROD_MY_KUBECONFIG} apply -f secrets.yaml")
        }
    }
}

 

vars/jmeterTest.groovy

#!/usr/bin/env groovy

def call(String servicePath) {
    sh "jmeter -j jmeter.save.saveservice.output_format=xml -n -t ${servicePath}jmeter.jmx -l ${servicePath}jmeter.report.jtl"
    sh "cp ${servicePath}jmeter.report.jtl ${servicePath}jmeter.report.${BUILD_NUMBER}.jtl"
    perfReport errorFailedThreshold:5, sourceDataFiles:"${servicePath}jmeter.report.jtl"
    sh "cat ${servicePath}jmeter.report.${BUILD_NUMBER}.jtl"

    sh """#!/bin/sh
                            grep '<failure>true</failure>' ${servicePath}jmeter.report.${BUILD_NUMBER}.jtl
                            if [ \$? = 0 ]
                            then
                                exit 1
                            else
                                exit 0
                            fi
                            """
}

 

vars/k8sCheckDeployStatus.groovy

#!/usr/bin/env groovy

def call(String nameSpaceName, String serviceName, String envName) {
    if (envName == 'dev') {
        sh("""
                    ATTEMPTS=0
                    ROLLOUT_STATUS_CMD='kubectl --kubeconfig  ${DEV_MY_KUBECONFIG} rollout status deployment/${serviceName}  -n ${nameSpaceName}-ns'
                    until \$ROLLOUT_STATUS_CMD || [ \$ATTEMPTS -eq 60 ]; do
                        \$ROLLOUT_STATUS_CMD
                        ATTEMPTS=\$((attempts + 1))
                        sleep 10
                    done
                 """)
    }
    if (envName == 'prod') {
        sh("""
                    ATTEMPTS=0
                    ROLLOUT_STATUS_CMD='kubectl --kubeconfig  ${PROD_MY_KUBECONFIG} rollout status deployment/${serviceName}  -n ${nameSpaceName}-ns'
                    until \$ROLLOUT_STATUS_CMD || [ \$ATTEMPTS -eq 60 ]; do
                        \$ROLLOUT_STATUS_CMD
                        ATTEMPTS=\$((attempts + 1))
                        sleep 10
                    done
                 """)
    }
}

 

vars/k8sDeploy.groovy

#!/usr/bin/env groovy

def call(String servicePath, String envName) {
    if (envName == 'dev') {
        sh("kubectl --kubeconfig  ${DEV_MY_KUBECONFIG} apply -f ${servicePath}deployment.yaml")
    }
    if (envName == 'prod') {
        sh("kubectl --kubeconfig  ${PROD_MY_KUBECONFIG} apply -f ${servicePath}deployment.yaml")
    }
}

 

vars/k8sGenerateYaml.groovy

#!/usr/bin/env groovy

def call(String namespaceName, String serviceName, String servicePath, String envName, String servicePort, String nodePort) {
    sh """sed "s/{{tagversion}}/${envName}-${BUILD_NUMBER}/g"  ${servicePath}deployment.yaml.tpl > ${servicePath}deployment.yaml """
    sh """sed -i 's/{{ServiceName}}/${serviceName}/g'  ${servicePath}deployment.yaml"""
    sh """sed -i 's/{{ServicePort}}/${servicePort}/g'  ${servicePath}deployment.yaml"""
    sh """sed -i 's/{{NodePort}}/${nodePort}/g'  ${servicePath}deployment.yaml"""
    sh """sed -i 's/{{NameSpaceName}}/${namespaceName}/g'  ${servicePath}deployment.yaml"""
}

 

The code in Jenkins demo secrets is as follows

apiVersion: v1
kind: Namespace
metadata:
  name: {{NameSpaceName}}-ns

---
apiVersion: v1
kind: Secret
metadata:
 name: harbor-key
 namespace: {{NameSpaceName}}-ns
type: kubernetes.io/dockerconfigjson
data:
 .dockerconfigjson: ewoJImF1dGhzIjogewoJCSIxOTIuMTY4LjQzLjk1IjogewoJCQkiYXV0aCI6ICJZV1J0YVc0Nk1USXpORFUyIgoJCX0sCgkJInJlZ2lzdHJ5LmNuLWJlaWppbmcuYWxpeXVuY3MuY29tIjogewoJCQkiYXV0aCI6ICI2Wm1JNUxpQTU0dXVPbU5vWlc1NWFYTm9hVEV5TXc9PSIKCQl9Cgl9Cn0=

  Dockerconfig JSON can be obtained in the following ways

docker login --username=admin --password=123456 192.168.43.95
cat ~/.docker/config.json | base64

Shared libraries referenced in Jenkinsfile

Dockerfile, Jenkinsfile, deployment.yaml.tpl, jmeter.jmx need to be written in advance in the project library

 

Dockerfile

#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
WORKDIR /app
EXPOSE 5001

FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /src
COPY ["FrontEnd/FrontEnd.csproj", "FrontEnd/"]
COPY ["Common/Common.csproj", "Common/"]
RUN dotnet restore "FrontEnd/FrontEnd.csproj"
COPY . .
WORKDIR "/src/FrontEnd"
RUN dotnet build "FrontEnd.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "FrontEnd.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "FrontEnd.dll"]

Jenkinsfile

#!/usr/bin/env groovy

@Library('share@master') _

run 'daprtest', 'frontend', './FrontEnd/', '5001', '31111', ['dev':'*/master', 'prod':'*/master']

deployment.yaml.tpl

apiVersion: v1
kind: Namespace
metadata:
  name: {{NameSpaceName}}-ns


---
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: statestore
  namespace: {{NameSpaceName}}-ns
spec:
  type: state.redis
  version: v1
  metadata:
  - name: redisHost
    value: 192.168.43.102:6379
  - name: redisPassword
    value: "123456"
  - name: actorStateStore
    value: "true"

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ServiceName}}
  namespace: {{NameSpaceName}}-ns
  labels:
    app: {{ServiceName}}
spec:
  replicas: 1
  selector:
    matchLabels:
      app: {{ServiceName}}
  template:
    metadata:
      namespace: {{NameSpaceName}}-ns
      labels:
        app: {{ServiceName}}
      annotations:
        dapr.io/enabled: "true"
        dapr.io/app-id: "{{ServiceName}}"
        dapr.io/app-port: "{{ServicePort}}"
    spec:
      imagePullSecrets:
      - name: harbor-key
      containers:
      - name: {{ServiceName}}
        image: 192.168.43.95/dapr/{{ServiceName}}:{{tagversion}}
        ports:
        - containerPort: {{ServicePort}}
        imagePullPolicy: Always

---
apiVersion: v1
kind: Service
metadata:
  namespace: {{NameSpaceName}}-ns
  name: {{ServiceName}}-svc
spec:
  type: NodePort
  selector:
    app: {{ServiceName}}
  ports:
    - port: {{ServicePort}}
      targetPort: {{ServicePort}}
      nodePort: {{NodePort}}

jmter.jmx is written as needed

New pipline

Where the URL points to the git address of your project

Building pipline

So far, we have completed a line of code in Jenkinsfile to deploy our project. If there is a new service, on the premise of writing Dockerfile, deployment.yaml.tpl (consistent) and jmeter.jmx, we only need to reference the run method through the shared library in Jenkinsfile

 

Tags: jenkins

Posted on Mon, 08 Nov 2021 13:35:15 -0500 by stevieontario