CI/CD pipeline su Jenkins

Kas tas Jenkins ir kodėl jis vis dar populiarus

Jei dirbate IT srityje, tikriausiai ne kartą girdėjote apie Jenkins. Šis automatizavimo serveris jau daugiau nei dešimtmetį yra vienas populiariausių įrankių, kai kalbama apie nuolatinę integraciją (CI) ir nuolatinį pristatymą (CD). Nors rinkoje atsirado naujų žaidėjų kaip GitLab CI, GitHub Actions ar CircleCI, Jenkins vis dar išlaiko tvirtą poziciją daugelyje organizacijų.

Kodėl taip yra? Pirma, Jenkins yra visiškai nemokamas ir atviro kodo. Antra, jis neįtikėtinai lankstus – galite jį pritaikyti beveik bet kokiam scenarijui. Trečia, ekosistema. Jenkins turi daugiau nei 1800 įskiepių, kurie leidžia integruoti praktiškai bet kokį įrankį ar technologiją, kurią naudojate savo projektuose.

Tiesa, Jenkins turi ir trūkumų. Jo sąsaja atrodo tarsi iš 2010-ųjų (nes iš tiesų tokia ir yra), o konfigūracija gali būti sudėtinga pradedantiesiems. Bet kai suprantate, kaip jis veikia, Jenkins tampa galingas ginklas jūsų arsenale.

Kaip veikia CI/CD principas praktikoje

Prieš gilinantis į Jenkins konfigūraciją, verta suprasti, ką iš tikrųjų reiškia CI/CD. Nuolatinė integracija (Continuous Integration) reiškia, kad kūrėjai dažnai – kartais net kelis kartus per dieną – įkelia savo kodo pakeitimus į bendrą repozitoriją. Kiekvienas toks įkėlimas automatiškai suaktyvina testų ir kompiliavimo procesus.

Nuolatinis pristatymas (Continuous Delivery) eina žingsnį toliau – jis užtikrina, kad kodas visada būtų paruoštas produkcijai. Tai nereiškia, kad kiekvienas pakeitimas automatiškai patenka į produkciją, bet kad jis galėtų ten patekti bet kuriuo momentu, jei tik paspausite mygtuką.

Praktikoje tai atrodo maždaug taip: kūrėjas užbaigia funkciją, padaro commit į Git, Jenkins automatiškai paima naują kodą, sukompiliuoja jį, paleidžia unit testus, integracinius testus, atlieka kodo analizę, sukuria Docker konteinerį ir, jei viskas gerai, įkelia jį į staging aplinką. Visas šis procesas vyksta automatiškai per kelias minutes.

Jenkins diegimas ir pradinė konfigūracija

Pradėkime nuo pagrindų. Jenkins galite paleisti keliais būdais – tiesiogiai serveryje, Docker konteineryje arba net Kubernetes klasteryje. Paprasčiausias būdas pradedantiesiems yra Docker:

docker run -p 8080:8080 -p 50000:50000 -v jenkins_home:/var/jenkins_home jenkins/jenkins:lts

Po kelių minučių Jenkins bus pasiekiamas adresu http://localhost:8080. Pirmą kartą prisijungiant reikės įvesti pradinį slaptažodį, kurį rasite konteinero loguose arba faile /var/jenkins_home/secrets/initialAdminPassword.

Pradinės konfigūracijos metu Jenkins pasiūlys įdiegti rekomenduojamus įskiepius. Mano patarimas – sutikite. Vėliau visada galėsite pridėti ar pašalinti tai, ko reikia. Tarp būtinų įskiepių tikrai turėtų būti Git, Pipeline, Blue Ocean (modernesnis UI), Docker Pipeline ir jūsų naudojamos programavimo kalbos įrankiai.

Sukūrę administratoriaus paskyrą, pirmiausia eikite į „Manage Jenkins” → „Configure System” ir nustatykite pagrindinius parametrus: Jenkins URL, administratoriaus el. paštą, Git konfigūraciją. Taip pat verta iš karto sukonfigūruoti „Manage Jenkins” → „Global Tool Configuration”, kur nurodote Java, Maven, Node.js ar kitų įrankių versijas ir vietas.

Pirmasis Pipeline: nuo Freestyle iki Jenkinsfile

Jenkins siūlo kelis būdus kurti darbus (jobs). Senasis būdas – Freestyle projektai, kur viską konfigūruojate per web sąsają. Bet šiuolaikinis ir teisingas būdas – Pipeline kaip kodas (Pipeline as Code), naudojant Jenkinsfile.

Jenkinsfile – tai Groovy kalbos failas, kuriame aprašote visą savo CI/CD procesą. Jis gyvena jūsų projekto repozitorijoje kartu su kodu, o tai reiškia, kad pipeline konfigūracija versijuojama, gali būti peržiūrėta code review metu ir keliasi kartu su projektu.

Paprasčiausias Jenkinsfile atrodo taip:


pipeline {
agent any

stages {
stage('Build') {
steps {
echo 'Building...'
sh 'npm install'
sh 'npm run build'
}
}

stage('Test') {
steps {
echo 'Testing...'
sh 'npm test'
}
}

stage('Deploy') {
steps {
echo 'Deploying...'
sh './deploy.sh'
}
}
}
}

Šis pavyzdys parodo tris pagrindinius etapus: kompiliavimą, testavimą ir deployment’ą. Kiekvienas stage vykdomas nuosekliai, ir jei vienas nepavyksta, kiti nevykdomi.

Sudėtingesni scenarijai ir best practices

Realūs projektai retai būna tokie paprasti. Dažniausiai reikia skirtingų aplinkų, sąlyginės logikos, paralelių procesų. Štai sudėtingesnis pavyzdys:


pipeline {
agent any

environment {
DOCKER_REGISTRY = 'registry.example.com'
APP_NAME = 'my-app'
}

stages {
stage('Checkout') {
steps {
checkout scm
}
}

stage('Build & Test') {
parallel {
stage('Backend') {
agent {
docker {
image 'maven:3.8-openjdk-11'
}
}
steps {
sh 'mvn clean package'
junit 'target/surefire-reports/*.xml'
}
}

stage('Frontend') {
agent {
docker {
image 'node:16'
}
}
steps {
sh 'npm ci'
sh 'npm run test:ci'
sh 'npm run build'
}
}
}
}

stage('Docker Build') {
steps {
script {
def version = sh(returnStdout: true, script: 'git describe --tags --always').trim()
docker.build("${DOCKER_REGISTRY}/${APP_NAME}:${version}")
}
}
}

stage('Deploy to Staging') {
when {
branch 'develop'
}
steps {
sh 'kubectl apply -f k8s/staging/'
}
}

stage('Deploy to Production') {
when {
branch 'main'
}
steps {
input message: 'Deploy to production?'
sh 'kubectl apply -f k8s/production/'
}
}
}

post {
success {
slackSend color: 'good', message: "Build succeeded: ${env.JOB_NAME} ${env.BUILD_NUMBER}"
}
failure {
slackSend color: 'danger', message: "Build failed: ${env.JOB_NAME} ${env.BUILD_NUMBER}"
}
}
}

Šiame pavyzdyje matome keletą svarbių dalykų. Pirma, naudojame environment sekciją globalinėms kintamiesiems. Antra, backend ir frontend testuojame paraleliai, taupydami laiką. Trečia, naudojame when sąlygas, kad skirtingose šakose vyktų skirtingi veiksmai. Ketvirta, production deployment’as reikalauja rankinio patvirtinimo. Penkta, post sekcijoje siunčiame pranešimus į Slack.

Saugumas ir credentials valdymas

Vienas didžiausių klausimų dirbant su Jenkins – kaip saugiai valdyti slaptažodžius, API raktus ir kitus jautrius duomenis. Jenkins turi integruotą credentials sistemą, bet ją reikia naudoti teisingai.

Niekada, kartoju, NIEKADA nerašykite slaptažodžių tiesiogiai į Jenkinsfile. Vietoj to, sukurkite credentials Jenkins’e („Manage Jenkins” → „Manage Credentials”) ir naudokite juos per environment kintamuosius:


pipeline {
agent any

environment {
DOCKER_CREDS = credentials('docker-registry-credentials')
AWS_CREDS = credentials('aws-access-key')
}

stages {
stage('Deploy') {
steps {
sh 'docker login -u $DOCKER_CREDS_USR -p $DOCKER_CREDS_PSW'
sh 'aws configure set aws_access_key_id $AWS_CREDS_USR'
sh 'aws configure set aws_secret_access_key $AWS_CREDS_PSW'
}
}
}
}

Jenkins automatiškai maskuoja šiuos kintamuosius loguose, todėl slaptažodžiai nebus matomi build output’e.

Dar geriau – naudokite išorinius secrets valdymo įrankius kaip HashiCorp Vault ar AWS Secrets Manager. Jenkins turi įskiepius, kurie leidžia dinamiškai gauti secrets iš šių sistemų build metu.

Taip pat svarbu apriboti prieigą prie Jenkins. Naudokite Role-Based Access Control (RBAC) įskiepį, kad skirtingi vartotojai turėtų skirtingas teises. Kūrėjai gali matyti ir paleisti build’us, bet tik DevOps komanda turėtų galėti keisti pipeline konfigūraciją ar valdyti credentials.

Monitoringas, optimizacija ir troubleshooting

Jenkins veikia puikiai, kol pradeda neveikti. Ir tada prasideda linksma medžioklė per logus. Keletas patarimų, kaip išvengti dažniausių problemų.

Pirma, stebėkite Jenkins resursų naudojimą. Jenkins gali tapti tikru resursų rijiku, ypač jei turite daug paralelių build’ų. Naudokite „Monitoring” įskiepį arba integruokite su Prometheus/Grafana, kad matytumėte CPU, atminties ir disko naudojimą realiu laiku.

Antra, valykite senus build’us. Kiekvienas build užima vietą diske, o per metus jų gali susikaupti tūkstančiai. Nustatykite build retention policy:


pipeline {
agent any

options {
buildDiscarder(logRotator(numToKeepStr: '30', artifactNumToKeepStr: '10'))
}

// ... likusios stages
}

Trečia, optimizuokite build laiką. Jei build’as trunka ilgiau nei 10-15 minučių, kažkas negerai. Naudokite Docker layer caching, Maven/Gradle dependency cache, paralelizuokite testus. Pavyzdžiui, Maven cache:


stage('Build') {
steps {
sh 'mvn -Dmaven.repo.local=.m2/repository clean package'
}
}

Ketvirta, kai kas nors neveikia, pirmiausia žiūrėkite į „Console Output”. Ten rasite visus build’o logus. Jei problema neaiški, padidinkite logging level’į pridėdami debug flag’us prie komandų.

Integracijos su kitais įrankiais

Jenkins tikroji jėga atsiskleidžia integruojant jį su kitais įrankiais. Štai keletas būtinų integracijų:

Git hooks – užuot rankiniu būdu paleidę build’ą, nustatykite webhook’us, kad Jenkins automatiškai reaguotų į naujus commit’us ar pull request’us. GitHub, GitLab ir Bitbucket visi tai palaiko.

SonarQube – kodo kokybės analizė. Integruokite SonarQube, kad automatiškai tikrintumėte kodo kokybę, security pažeidžiamumus ir technical debt:


stage('Code Quality') {
steps {
withSonarQubeEnv('SonarQube') {
sh 'mvn sonar:sonar'
}
}
}

Jira – automatiškai atnaujinkite task’ų statusus pagal build rezultatus. Galite net automatiškai perkelti task’us į „Done” kolonėlę, kai kodas pasiekia production.

Slack/Teams – pranešimai apie build’ų statusą. Bet būkite atsargūs – niekas nenori gauti 50 pranešimų per dieną. Siųskite pranešimus tik apie failed build’us arba successful deployment’us į production.

Artifactory/Nexus – jei kuriate bibliotekus ar reusable komponentus, naudokite artifact repository. Jenkins gali automatiškai publikuoti built artifacts į šias sistemas.

Kas toliau: Jenkins evoliucija ir alternatyvos

Jenkins nėra tobulas, ir tai pripažįsta net jo didžiausi fanai. Jis gali būti lėtas, sunkiai prižiūrimas, o jo UI… na, apie tai jau kalbėjome. Bet Jenkins bendruomenė dirba ties pagerinimais.

Jenkins X – tai bandymas modernizuoti Jenkins Kubernetes erai. Jis sukurtas cloud-native aplikacijoms ir naudoja GitOps principus. Tiesa, Jenkins X yra gana sudėtingas ir dar ne visai subrendęs, bet verta stebėti.

Alternatyvų rinkoje tikrai netrūksta. GitLab CI/CD yra puikus pasirinkimas, jei jau naudojate GitLab. GitHub Actions idealiai integruojasi su GitHub. CircleCI ir Travis CI siūlo cloud-based sprendimus be serverių administravimo galvos skausmo. ArgoCD ir Flux puikiai tinka Kubernetes deployment’ams.

Bet štai kas įdomu – daugelis organizacijų naudoja hibridinį požiūrį. Jenkins CI daliai, ArgoCD deployment’ams. Arba GitHub Actions lengviems projektams, Jenkins sudėtingesnėms enterprise sistemoms. Nebūtina rinktis vieno įrankio – rinkitės tai, kas geriausiai tinka jūsų kontekstui.

Jei tik pradedate, mano patarimas būtų toks: pradėkite nuo ko nors paprastesnio kaip GitHub Actions ar GitLab CI. Išmokite CI/CD principų. O kai suprasite, ko jums tikrai reikia, tada galėsite apsvarstyti Jenkins, jei jo lankstumas ir galimybės atsveria jo sudėtingumą.

Galiausiai, nesvarbu, kurį įrankį pasirinksite, svarbiausia yra pats CI/CD principas. Automatizuokite, testuokite dažnai, deploy’inkite mažais žingsniais. Jenkins čia tik įrankis, padedantis tai pasiekti, bet ne tikslas pats savaime.

Daugiau

Yandex Cloud: Rusijos debesų platforma