Kami menguasai tugas untuk penerapan di GKE tanpa plugin, SMS, dan pendaftaran. Mengintip di bawah jaket Jenkins dengan satu mata

Semuanya dimulai ketika ketua tim dari salah satu tim pengembangan kami meminta kami untuk menguji aplikasi baru mereka, yang telah dimasukkan ke dalam container sehari sebelumnya. Saya memasangnya. Setelah sekitar 20 menit, permintaan diterima untuk memperbarui aplikasi, karena bagian yang sangat penting telah selesai di sana. Saya memperbarui. Beberapa jam kemudian ... yah, Anda



sudah menebak apa yang mulai terjadi selanjutnya ... Harus saya akui, saya agak malas (apakah saya mengakuinya lebih awal? Tidak?), Dan, mengingat fakta bahwa pimpinan tim memiliki akses ke Jenkins, di mana Kami memiliki semua CI / CD, saya pikir: biarkan dia mengerahkan dirinya sesuka dia! Saya teringat sebuah lelucon: beri seseorang ikan dan dia akan kenyang untuk hari itu; panggil seseorang Puas dan dia akan Puas sepanjang hidupnya. Dan dia pergi bermain-main dengan pekerjaan itu, yang akan dapat menerapkan wadah ke dalam Kuber dengan aplikasi versi yang berhasil dirakit dan mentransfer nilai ENV apa pun ke dalamnya (kakek saya, seorang ahli filologi, guru bahasa Inggris di masa lalu, sekarang akan memutar jarinya ke pelipisnya dan melihat saya dengan sangat ekspresif setelah membaca ini kalimat).



Jadi, dalam sebuah posting saya akan berbicara tentang bagaimana saya belajar:



  1. Memperbarui pekerjaan secara dinamis di Jenkins dari pekerjaan itu sendiri atau dari pekerjaan lain;
  2. Hubungkan ke konsol cloud (Cloud shell) dari node dengan agen Jenkins terpasang;
  3. Terapkan beban kerja ke Google Kubernetes Engine.


Nyatanya, saya, tentu saja, sedikit licik. Diasumsikan bahwa setidaknya sebagian dari infrastruktur Anda ada di Google Cloud, dan oleh karena itu Anda adalah penggunanya dan, tentu saja, Anda memiliki akun GCP. Tapi catatannya bukan tentang itu.



Ini adalah lembar contekan saya berikutnya. Saya hanya ingin menulis catatan seperti itu dalam satu kasus: Saya punya masalah di depan saya, saya awalnya tidak tahu bagaimana menyelesaikannya, solusinya tidak googled dalam bentuk akhirnya, jadi saya googling beberapa bagian dan akhirnya menyelesaikan masalah. Dan agar di masa depan, ketika saya lupa bagaimana saya melakukannya, saya tidak perlu lagi mencari di Google sepotong demi sepotong dan menyusunnya bersama-sama, saya menulis sendiri contekan semacam itu.

Disclaimer: 1. Β« Β», best practice . Β« Β» .

2. , , , β€” .

Jenkins



Saya memperkirakan pertanyaan Anda: apa hubungannya pembaruan dinamis pekerjaan dengan itu? Saya memasukkan nilai parameter string dengan pegangan dan lanjutkan!



Jawabannya adalah: Saya benar-benar malas, saya tidak suka ketika mereka mengeluh: Misha, penerapannya macet, semuanya hilang! Anda mulai mencari, dan ada kesalahan ketik pada nilai beberapa parameter peluncuran tugas. Oleh karena itu, saya lebih suka melakukan segala sesuatunya selengkap mungkin. Jika memungkinkan untuk mencegah pengguna memasukkan data secara langsung dengan memberikan daftar nilai untuk dipilih, maka saya mengatur pemilihan.



Rencananya adalah sebagai berikut: buat pekerjaan di Jenkins, di mana, sebelum diluncurkan, dimungkinkan untuk memilih versi dari daftar, menentukan nilai untuk parameter yang diteruskan ke penampung melalui ENV , lalu mengumpulkan penampung dan mendorongnya ke Registri Penampung. Lebih jauh dari sana, kontainer diluncurkan di kubera sebagaibeban kerja dengan parameter yang ditentukan dalam pekerjaan.



Kami tidak akan mempertimbangkan proses pembuatan dan konfigurasi pekerjaan di Jenkins, ini offtopic. Kami akan menganggap bahwa tugas sudah siap. Untuk mengimplementasikan daftar versi yang dapat diperbarui, kita memerlukan dua hal: daftar sumber yang ada dengan nomor versi yang valid secara apriori dan variabel tipe parameter Pilihan dalam tugas. Dalam contoh kami, biarkan variabel diberi nama BUILD_VERSION , kami tidak akan membahasnya secara detail. Tapi mari kita lihat lebih dekat daftar sumbernya.



Tidak banyak pilihan. Dua segera terlintas di benak saya:



  • Gunakan API akses jarak jauh yang ditawarkan Jenkins kepada penggunanya;
  • Buat kueri konten folder repositori jarak jauh (dalam kasus kami, ini adalah JFrog Artifactory, yang tidak penting).


Jenkins Remote access API



Menurut tradisi baik yang sudah mapan, saya lebih suka menghindari penjelasan yang panjang.

Saya hanya akan mengizinkan terjemahan gratis dari sebagian paragraf pertama dari halaman pertama dokumentasi API :

Jenkins menyediakan API untuk akses jarak jauh yang dapat dibaca mesin ke fungsinya. <...> Akses jarak jauh ditawarkan dengan gaya REST. Ini berarti bahwa tidak ada satu titik masuk untuk semua kemampuan, dan sebagai gantinya URL seperti " ... / api / " digunakan, di mana " ... " adalah objek tempat kapabilitas API diterapkan.
Dengan kata lain, jika tugas penerapan, yang sedang kita bicarakan saat ini, tersedia di alamat http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build, maka peluit API untuk tugas ini tersedia di Berikutnya, kita punya pilihan dalam bentuk apa untuk menerima keluaran. Mari kita bahas XML, karena dalam kasus ini API hanya mengizinkan pemfilteran. Mari kita coba untuk mendapatkan daftar semua pekerjaan yang berjalan. Kami hanya tertarik pada nama assembly ( displayName ) dan hasilnya ( result ):http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/











http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/xml?tree=allBuilds[displayName,result]


Terjadi?



Sekarang mari kita filter hanya peluncuran yang berakhir dengan hasil SUKSES . Kami menggunakan argumen & exclude dan meneruskannya ke nilai yang tidak sama dengan SUCCESS sebagai parameter . Ya ya. Negasi ganda adalah pernyataan. Kami mengecualikan semua yang tidak menarik bagi kami:



http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/xml?tree=allBuilds[displayName,result]&exclude=freeStyleProject/allBuild[result!='SUCCESS']


Tangkapan layar dari daftar berhasil




Nah, hanya untuk bersenang-senang, mari kita pastikan bahwa filter tidak menipu kita (filter tidak pernah berbohong!) Dan menampilkan daftar yang "tidak berhasil":



http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/xml?tree=allBuilds[displayName,result]&exclude=freeStyleProject/allBuild[result='SUCCESS']


Tangkapan layar dari daftar yang tidak berhasil




Daftar versi dari folder di server jauh



Ada juga cara kedua untuk mendapatkan daftar versi. Saya menyukainya bahkan lebih dari panggilan API Jenkins. Nah, karena jika aplikasi sudah berhasil dibangun, maka sudah dipaketkan dan dimasukkan ke dalam repositori di folder yang sesuai. Seperti, repositori secara default adalah repositori versi aplikasi yang berfungsi. Suka. Nah, mari kita tanyakan versi apa yang ada di penyimpanan. Kami akan menggulung, menggrep, dan membongkar folder jarak jauh. Jika seseorang tertarik dengan unliner, maka itu di bawah spoiler.



Perintah satu baris
: , , . :



curl -H "X-JFrog-Art-Api:VeryLongAPIKey" -s http://arts.myre.po/artifactory/awesomeapp/ | sed 's/a href=//' | grep "$(date +%b)-$(date +%Y)\|$(date +%b --date='-1 month')-$(date +%Y)" | awk '{print $1}' | grep -oP '>\K[^/]+' )




Penyiapan pekerjaan dan file konfigurasi pekerjaan di Jenkins



Dengan sumber daftar versi diurutkan. Sekarang mari kita sekrup daftar yang dihasilkan ke dalam tugas. Bagi saya, solusi yang jelas adalah menambahkan langkah dalam pekerjaan pembuatan aplikasi. Langkah yang akan dilakukan jika hasilnya adalah "sukses".



Buka pengaturan tugas perakitan dan gulir ke bawah. Klik pada tombol: Tambahkan langkah build -> Langkah bersyarat (tunggal) . Dalam pengaturan langkah, pilih kondisi status pembuatan saat ini , setel nilai SUKSES , tindakan yang akan dilakukan jika perintah Jalankan shell berhasil .



Dan sekarang bagian yang menyenangkan. Jenkins menyimpan konfigurasi pekerjaan dalam file. Dalam format XML. Sepanjang jalanhttp://--/config.xmlKarenanya, Anda dapat mengunduh file konfigurasi, mengeditnya sesuai kebutuhan, dan meletakkannya di tempat asalnya.



Ingat, di atas kita sepakat bahwa kita akan membuat parameter BUILD_VERSION untuk daftar versi ?



Mari unduh file konfigurasi dan lihat isinya. Hanya untuk memastikan bahwa parameternya ada dan benar-benar tepat.



Tangkapan layar di bawah spoiler.



Cuplikan config.xml Anda akan terlihat sama. Kecuali bahwa konten elemen pilihan belum ada




Apakah Anda yakin? Baiklah, kami sedang menulis skrip yang akan dieksekusi jika build berhasil.

Skrip akan menerima daftar versi, mengunduh file konfigurasi, menulis daftar versi ke dalamnya di tempat yang kita butuhkan, dan kemudian mengembalikannya. Iya. Semuanya benar. Tulis daftar versi dalam XML ke tempat di mana sudah ada daftar versi (akan berada di masa mendatang, setelah peluncuran pertama skrip). Saya tahu masih ada beberapa pecinta ekspresi reguler di dunia. Saya bukan milik mereka. Harap instal xmlstarler di komputer tempat konfigurasi akan diedit. Menurut saya ini bukan harga yang mahal untuk menghindari pengeditan XML dengan sed.



Di bawah spoiler, saya mengutip kode yang menjalankan seluruh urutan yang dijelaskan di atas.



Kami menulis ke konfigurasi daftar versi dari folder di server jauh
#!/bin/bash
##############  
curl -X GET -u username:apiKey http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_k8s/config.xml -o appConfig.xml

##############     xml-   
xmlstarlet ed --inplace -d '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a[@class="string-array"]' appConfig.xml

xmlstarlet ed --inplace --subnode '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]' --type elem -n a appConfig.xml

xmlstarlet ed --inplace --insert '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a' --type attr -n class -v string-array appConfig.xml

##############       
readarray -t vers < <( curl -H "X-JFrog-Art-Api:Api:VeryLongAPIKey" -s http://arts.myre.po/artifactory/awesomeapp/ | sed 's/a href=//' | grep "$(date +%b)-$(date +%Y)\|$(date +%b --date='-1 month')-$(date +%Y)" | awk '{print $1}' | grep -oP '>\K[^/]+' )

##############       
printf '%s\n' "${vers[@]}" | sort -r | \
                while IFS= read -r line
                do
                    xmlstarlet ed --inplace --subnode '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a[@class="string-array"]' --type elem -n string -v "$line" appConfig.xml
                done

##############   
curl -X POST -u username:apiKey http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_k8s/config.xml --data-binary @appConfig.xml

##############     
rm -f appConfig.xml




Jika Anda lebih menyukai opsi untuk mendapatkan versi dari Jenkins dan Anda malas seperti saya, maka di bawah spoiler kode yang sama, tetapi daftarnya dari Jenkins:



Kami menulis daftar versi dari Jenkins ke konfigurasi
: , . , awk . .



#!/bin/bash
##############  
curl -X GET -u username:apiKey http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_k8s/config.xml -o appConfig.xml

##############     xml-   
xmlstarlet ed --inplace -d '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a[@class="string-array"]' appConfig.xml

xmlstarlet ed --inplace --subnode '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]' --type elem -n a appConfig.xml

xmlstarlet ed --inplace --insert '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a' --type attr -n class -v string-array appConfig.xml

##############       Jenkins
curl -g -X GET -u username:apiKey 'http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/xml?tree=allBuilds[displayName,result]&exclude=freeStyleProject/allBuild[result!=%22SUCCESS%22]&pretty=true' -o builds.xml

##############       XML
readarray vers < <(xmlstarlet sel -t -v "freeStyleProject/allBuild/displayName" builds.xml | awk -F":" '{print $2}')

##############       
printf '%s\n' "${vers[@]}" | sort -r | \
                while IFS= read -r line
                do
                    xmlstarlet ed --inplace --subnode '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a[@class="string-array"]' --type elem -n string -v "$line" appConfig.xml
                done

##############   
curl -X POST -u username:apiKey http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_k8s/config.xml --data-binary @appConfig.xml

##############     
rm -f appConfig.xml




Secara teori, jika Anda menguji kode yang ditulis berdasarkan contoh di atas, maka dalam tugas penerapan Anda seharusnya sudah memiliki daftar tarik-turun dengan versi. Ini sesuatu seperti tangkapan layar di bawah spoiler.



Daftar versi yang diselesaikan dengan benar




Jika semuanya berfungsi, salin dan tempel skrip di perintah Jalankan shell dan simpan perubahannya.



Koneksi cloud shell



Kolektor ada di dalam wadah kami. Kami menggunakan Ansible sebagai pengiriman aplikasi dan manajer konfigurasi kami. Karenanya, ketika datang untuk membangun kontainer, tiga opsi muncul: instal Docker di Docker, instal Docker di mesin dengan Ansible, atau buat container di konsol cloud. Kami telah setuju untuk bungkam tentang plugin untuk Jenkins di artikel ini. Ingat?



Saya memutuskan: yah, karena wadah "di luar kotak" dapat dipasang di cloud console, lalu mengapa memagari kebun sayur? Tetap bersih, bukan? Saya ingin mengumpulkan container dengan Jenkins di cloud console, lalu memasukkannya ke dalam kuber dari sana. Selain itu, Google memiliki saluran yang sangat kaya di dalam infrastruktur, yang akan memberikan efek menguntungkan pada kecepatan penerapan.



Dua hal yang diperlukan untuk terhubung ke konsol cloud: gclouddan hak akses ke Google Cloud API untuk instance VM tempat koneksi ini akan dibuat.



Bagi mereka yang berencana untuk terhubung sama sekali tidak dari Google cloud
. , *nix' .



, β€” . β€” .



Cara termudah untuk memberikan izin adalah melalui antarmuka web.



  1. Hentikan instance VM yang akan Anda hubungkan ke konsol cloud di masa mendatang.
  2. Buka Detail Instance dan klik Edit .
  3. Di bagian paling bawah halaman, pilih cakupan akses instance. Akses penuh ke semua Cloud API .



    Screenshot


  4. Simpan perubahan Anda dan mulai instance.


Setelah VM selesai booting, sambungkan ke VM melalui SSH dan pastikan sambungan berhasil. Gunakan perintah:



gcloud alpha cloud-shell ssh


Koneksi yang berhasil terlihat seperti ini




Terapkan ke GKE



Karena kami berusaha dengan segala cara yang mungkin untuk sepenuhnya beralih ke IaC (Infrastucture as a Code), kami menyimpan file galangan di gita. Ini di satu sisi. Deployment dalam kubernetes dijelaskan oleh file yaml, yang hanya digunakan oleh tugas ini, yang juga seperti sebuah kode. Ini di sisi lain. Secara umum, maksud saya rencananya adalah sebagai berikut:



  1. Kami mengambil nilai variabel BUILD_VERSION dan, secara opsional, nilai variabel yang akan diteruskan melalui ENV .
  2. Mendownload file galangan dari gita.
  3. Menghasilkan yaml untuk penerapan.
  4. Unggah kedua file ini melalui scp ke konsol awan.
  5. Buat wadah di sana dan dorong ke registri Penampung
  6. Kami menerapkan file penerapan beban ke Kuber.


Mari lebih spesifik. Sejak kita mulai berbicara tentang ENV , misalkan kita perlu meneruskan nilai dari dua parameter: PARAM1 dan PARAM2 . Tambahkan tugas mereka untuk penerapan, ketik - Parameter String .



Screenshot




Kami akan menghasilkan yaml hanya dengan mengarahkan echo ke file. Diasumsikan, tentu saja, bahwa Anda memiliki PARAM1 dan PARAM2 di dockerfile , nama pemuatannya adalah awesomeapp , dan container yang dirakit dengan aplikasi versi yang ditentukan ada di registry Container di sepanjang jalur gcr.io/awesomeapp/awesomeapp- $ BUILD_VERSION , di mana $ BUILD_VERSION hanya dipilih dari daftar tarik-turun.



Mendaftar perintah
touch deploy.yaml
echo "apiVersion: apps/v1" >> deploy.yaml
echo "kind: Deployment" >> deploy.yaml
echo "metadata:" >> deploy.yaml
echo "  name: awesomeapp" >> deploy.yaml
echo "spec:" >> deploy.yaml
echo "  replicas: 1" >> deploy.yaml
echo "  selector:" >> deploy.yaml
echo "    matchLabels:" >> deploy.yaml
echo "      run: awesomeapp" >> deploy.yaml
echo "  template:" >> deploy.yaml
echo "    metadata:" >> deploy.yaml
echo "      labels:" >> deploy.yaml
echo "        run: awesomeapp" >> deploy.yaml
echo "    spec:" >> deploy.yaml
echo "      containers:" >> deploy.yaml
echo "      - name: awesomeapp" >> deploy.yaml
echo "        image: gcr.io/awesomeapp/awesomeapp-$BUILD_VERSION:latest" >> deploy.yaml
echo "        env:" >> deploy.yaml
echo "        - name: PARAM1" >> deploy.yaml
echo "          value: $PARAM1" >> deploy.yaml
echo "        - name: PARAM2" >> deploy.yaml
echo "          value: $PARAM2" >> deploy.yaml




Setelah menghubungkan menggunakan gcloud alpha cloud-shell ssh ke agen Jenkins, mode interaktif tidak tersedia, jadi kami mengirim perintah ke konsol cloud menggunakan parameter --command .



Kami membersihkan folder rumah di konsol cloud dari dockerfile lama:



gcloud alpha cloud-shell ssh --command="rm -f Dockerfile"


Kami meletakkan dockerfile yang baru diunduh ke dalam folder beranda konsol cloud menggunakan scp:



gcloud alpha cloud-shell scp localhost:./Dockerfile cloudshell:~


Kami mengumpulkan, memberi tag, dan mendorong penampung ke registri Penampung:



gcloud alpha cloud-shell ssh --command="docker build -t awesomeapp-$BUILD_VERSION ./ --build-arg BUILD_VERSION=$BUILD_VERSION --no-cache"
gcloud alpha cloud-shell ssh --command="docker tag awesomeapp-$BUILD_VERSION gcr.io/awesomeapp/awesomeapp-$BUILD_VERSION"
gcloud alpha cloud-shell ssh --command="docker push gcr.io/awesomeapp/awesomeapp-$BUILD_VERSION"


Kami melakukan hal yang sama dengan file penerapan. Perhatikan bahwa perintah di bawah ini menggunakan nama fiktif untuk cluster tempat penerapan dilakukan ( awsm-cluster ) dan nama proyek ( awesome-project ) tempat cluster berada.



gcloud alpha cloud-shell ssh --command="rm -f deploy.yaml"
gcloud alpha cloud-shell scp localhost:./deploy.yaml cloudshell:~
gcloud alpha cloud-shell ssh --command="gcloud container clusters get-credentials awsm-cluster --zone us-central1-c --project awesome-project && \
kubectl apply -f deploy.yaml"


Kami memulai tugas, membuka keluaran konsol dan berharap untuk melihat pembuatan kontainer berhasil.



Screenshot




Dan kemudian penerapan wadah yang dirakit berhasil



Screenshot




Saya sengaja mengabaikan pengaturan Ingress . Untuk satu alasan sederhana: setelah mengonfigurasinya untuk beban kerja dengan nama tertentu, itu akan tetap beroperasi, tidak peduli berapa banyak penerapan dengan nama ini yang dilakukan. Secara umum, ini sedikit di luar cakupan sejarah.



Bukan kesimpulan



Semua langkah di atas, mungkin, tidak dapat dilakukan, tetapi cukup menginstal beberapa plugin untuk Jenkins, muuulion mereka. Tapi entah kenapa saya tidak suka plugin. Nah, lebih tepatnya, saya menggunakan mereka hanya karena putus asa.



Dan saya hanya ingin mengambil beberapa topik baru untuk saya. Teks di atas juga merupakan cara untuk berbagi temuan yang saya buat, memecahkan masalah yang dijelaskan di awal. Berbagilah dengan mereka yang, seperti, sama sekali bukan serigala jahat di kalangan pengembang. Jika temuan saya membantu setidaknya seseorang, saya akan senang.



All Articles