
Dalam praktik kami, kami sering menghadapi tugas mengadaptasi aplikasi klien untuk berjalan di Kubernetes. Saat melakukan pekerjaan ini, sejumlah masalah khas muncul. Kami baru-baru ini membahas salah satunya di artikel
File lokal saat mentransfer aplikasi ke Kubernetes , dan yang lainnya, yang sudah dikaitkan dengan proses CI / CD, akan dijelaskan dalam artikel ini.
Perintah sewenang-wenang dengan Helm dan werf
Aplikasi tidak hanya logika bisnis dan data, tetapi juga seperangkat perintah sewenang-wenang yang harus dijalankan untuk pembaruan yang berhasil. Ini dapat berupa, misalnya, migrasi untuk database, "pelayan" untuk ketersediaan sumber daya eksternal, beberapa transkoder atau pembongkar paket, pendaftar dalam Penelusuran Layanan eksternal - Anda dapat memenuhi tugas yang berbeda pada proyek yang berbeda.
Apa yang ditawarkan Kubernetes untuk memecahkan masalah seperti itu?
Kubernetes tahu cara menjalankan kontainer sebagai pod, jadi solusi standarnya adalah menjalankan perintah dari sebuah gambar. Untuk melakukan ini, ada
pekerjaan primitif di Kubernetes yang memungkinkan Anda untuk menjalankan pod dengan wadah aplikasi dan melacak penyelesaian pod ini.
Helm melangkah lebih jauh dan menyarankan untuk meluncurkan Job di berbagai tahap proses penempatan. Kita berbicara tentang
kait Helm yang dengannya Anda dapat menjalankan Pekerjaan sebelum atau setelah memperbarui manifes sumber daya. Dalam pengalaman kami, ini adalah fitur Helm yang luar biasa yang dapat digunakan untuk menyelesaikan tugas penempatan.
Namun, tidak mungkin untuk mendapatkan informasi terkini tentang keadaan objek selama
peluncuran di Helm, oleh karena itu kami menggunakan utilitas
werf , yang memungkinkan untuk memantau status sumber daya selama peluncuran langsung dari sistem CI dan, jika terjadi kegagalan, diagnosa kerusakan lebih cepat.
Ternyata, fitur-fitur berguna Helm dan werf kadang-kadang saling eksklusif, tetapi selalu ada jalan keluar. Pertimbangkan bagaimana Anda dapat memantau status sumber daya dan menjalankan perintah sewenang-wenang pada contoh migrasi.
Menjalankan migrasi sebelum rilis
Bagian integral dari rilis aplikasi basis data apa pun adalah memperbarui skema data. Penerapan standar untuk aplikasi yang menerapkan migrasi dengan menjalankan perintah terpisah menyiratkan langkah-langkah berikut:
- pembaruan basis kode;
- mulai migrasi;
- mengalihkan lalu lintas ke versi aplikasi yang baru.
Di dalam Kubernetes, prosesnya harus sama, tetapi disesuaikan dengan yang kita butuhkan:
- meluncurkan wadah dengan kode baru, yang mungkin berisi kumpulan migrasi baru;
- mulai proses menerapkan migrasi di dalamnya, lakukan ini sebelum memperbarui versi aplikasi.
Pertimbangkan opsi ketika
database untuk aplikasi sudah berjalan dan kami tidak perlu menggunakannya sebagai bagian dari rilis yang menggunakan aplikasi. Dua kait cocok untuk menerapkan migrasi:
pre-install
- ini bekerja pada Helm-rilis pertama dari aplikasi setelah memproses semua template, tetapi sebelum membuat sumber daya di Kubernetes;pre-upgrade
- berfungsi saat memperbarui rilis dan pengoperasian Helm, seperti pre-install
, setelah memproses templat, tetapi sebelum membuat sumber daya di Kubernetes.
Contoh pekerjaan menggunakan Helm dan dua kait disebutkan:
--- apiVersion: batch/v1 kind: Job metadata: name: {{ .Chart.Name }}-apply-migrations annotations: "helm.sh/hook": pre-install,pre-upgrade spec: activeDeadlineSeconds: 60 backoffLimit: 0 template: metadata: name: {{ .Chart.Name }}-apply-migrations spec: imagePullSecrets: - name: {{ required ".Values.registry.secret_name required" .Values.registry.secret_name }} containers: - name: job command: ["/usr/bin/php7.2", "artisan", "migrate", "--force"] {{ tuple "backend" . | include "werf_container_image" | indent 8 }} env: {{ tuple "backend" . | include "werf_container_env" | indent 8 }} - name: DB_HOST value: postgres restartPolicy: Never
Catatan : templat YAML di atas dibuat dengan mempertimbangkan spesifikasi werf. Untuk menyesuaikannya dengan Helm "bersih", itu sudah cukup:- ganti
{{ tuple "backend" . | include "werf_container_image" | indent 8 }}
{{ tuple "backend" . | include "werf_container_image" | indent 8 }}
{{ tuple "backend" . | include "werf_container_image" | indent 8 }}
ke gambar kontainer yang Anda butuhkan; - hapus baris
{{ tuple "backend" . | include "werf_container_env" | indent 8 }}
{{ tuple "backend" . | include "werf_container_env" | indent 8 }}
{{ tuple "backend" . | include "werf_container_env" | indent 8 }}
, yang ditentukan dalam tombol env
.
Jadi, templat Helm ini perlu ditambahkan ke direktori
.helm/templates
, yang sudah berisi sisa sumber daya rilis. Ketika
werf deploy --stages-storage :local
dipanggil, semua templat akan diproses terlebih dahulu, dan kemudian mereka akan dimuat ke dalam cluster Kubernetes.
Memulai migrasi selama proses rilis
Opsi di atas menyiratkan penggunaan migrasi untuk kasus ketika database sudah berjalan. Tetapi bagaimana jika kita perlu meluncurkan tinjauan cabang untuk aplikasi, dan
database diluncurkan dengan aplikasi dalam satu rilis?
NB : Anda mungkin mengalami masalah serupa ketika meluncurkan ke lingkungan produksi jika Anda menggunakan Layanan dengan titik akhir yang berisi alamat IP dari database untuk terhubung ke database.Dalam hal ini, kait
pre-install
dan
pre-upgrade
tidak cocok untuk kami, karena aplikasi akan mencoba menerapkan migrasi ke database yang
belum ada . Oleh karena itu, migrasi perlu dilakukan
setelah rilis.
Saat menggunakan Helm, tugas seperti itu dapat dicapai, karena
tidak memonitor status aplikasi. Setelah memuat sumber daya di Kubernetes, pos kait
selalu menyala:
post-install
- setelah memuat semua sumber daya dalam K8 pada rilis pertama;post-upgrade
- setelah memperbarui semua sumber daya di K8 ketika memperbarui rilis.
Namun, seperti yang kami sebutkan di atas,
werf memiliki sistem pelacakan sumber daya selama rilis. Saya akan membahas ini lebih detail:
- Untuk pelacakan, werf menggunakan kemampuan perpustakaan kubedog , yang sudah kita bicarakan di blog.
- Fitur ini di werf memungkinkan kita untuk secara unik menentukan status rilis dan menampilkan informasi pada penyelesaian penyebaran yang berhasil atau tidak berhasil dalam antarmuka sistem CI / CD.
- Tanpa menerima informasi ini, orang tidak dapat berbicara tentang otomatisasi proses rilis, karena penciptaan sumber daya yang sukses di kluster Kubernetes hanyalah salah satu tahapan. Misalnya, aplikasi mungkin tidak memulai karena konfigurasi yang salah atau karena masalah jaringan, tetapi untuk melihat ini setelah
helm upgrade
, Anda harus melakukan langkah-langkah tambahan.
Sekarang kembali ke aplikasi migrasi pada post-hook Helm. Masalah yang kami temui:
- Banyak aplikasi sebelum meluncurkan satu atau lain cara memeriksa keadaan sirkuit dalam database. Oleh karena itu, tanpa migrasi baru, aplikasi mungkin tidak dimulai.
- Karena werf, secara default, memastikan bahwa semua objek dalam status
Ready
, kait pos tidak akan berfungsi dan migrasi akan gagal. - Objek pelacakan dapat dinonaktifkan melalui anotasi tambahan, tetapi kemudian tidak mungkin untuk mendapatkan informasi yang dapat dipercaya tentang hasil penyebaran.
Akibatnya, kami sampai pada hal berikut:
- Pekerjaan dibuat sebelum sumber daya utama, sehingga tidak perlu menggunakan kait Helm untuk migrasi .
- Namun, pekerjaan dengan migrasi harus dijalankan di setiap penggunaan. Agar ini terjadi, Ayub harus memiliki nama unik (acak): dalam hal ini, untuk Helm, ini adalah setiap kali objek baru dalam rilis, yang akan dibuat di Kubernetes.
- Dengan peluncuran seperti itu, tidak masuk akal untuk khawatir bahwa Ayub akan terakumulasi dengan migrasi, karena semuanya akan memiliki nama yang unik, dan Ayub sebelumnya dihapus dengan rilis baru.
- Pekerjaan dengan migrasi harus memiliki init-container yang memeriksa ketersediaan database - jika tidak kita akan mendapatkan penyebaran yang dibatalkan (Pekerjaan akan jatuh pada init-container).
Konfigurasi yang dihasilkan terlihat seperti ini:
--- apiVersion: batch/v1 kind: Job metadata: name: {{ printf "%s-apply-migrations-%s" .Chart.Name (now | date "2006-01-02-15-04-05") }} spec: activeDeadlineSeconds: 60 backoffLimit: 0 template: metadata: name: {{ printf "%s-apply-migrations-%s" .Chart.Name (now | date "2006-01-02-15-04-05") }} spec: imagePullSecrets: - name: {{ required ".Values.registry.secret_name required" .Values.registry.secret_name }} initContainers: - name: wait-db image: alpine:3.6 ommand: ["/bin/sh", "-c", "while ! nc -z postgres 5432; do sleep 1; done;"] containers: - name: job command: ["/usr/bin/php7.2", "artisan", "migrate", "--force"] {{ tuple "backend" . | include "werf_container_image" | indent 8 }} env: {{ tuple "backend" . | include "werf_container_env" | indent 8 }} - name: DB_HOST value: postgres restartPolicy: Never
NB : Sebenarnya, wadah init untuk memeriksa ketersediaan basis data paling baik digunakan.Contoh templat universal untuk semua operasi penyebaran
Namun, operasi yang perlu dilakukan selama rilis mungkin lebih dari peluncuran migrasi yang telah disebutkan. Anda dapat mengontrol urutan pelaksanaan Ayub tidak hanya melalui jenis kait, tetapi juga dengan
menetapkan bobot masing-masing - melalui anotasi
helm.sh/hook-weight
. Kait diurutkan berdasarkan berat dalam urutan menaik, dan jika beratnya sama, berdasarkan nama sumber.
Dengan sejumlah besar Pekerjaan, nyaman untuk membuat templat universal untuk Pekerjaan, dan meletakkan konfigurasi dalam
values.yaml
. Yang terakhir mungkin terlihat seperti ini:
deploy_jobs: - name: migrate command: '["/usr/bin/php7.2", "artisan", "migrate", "--force"]' activeDeadlineSeconds: 120 when: production: 'pre-install,pre-upgrade' staging: 'pre-install,pre-upgrade' _default: '' - name: cache-clear command: '["/usr/bin/php7.2", "artisan", "responsecache:clear"]' activeDeadlineSeconds: 60 when: _default: 'post-install,post-upgrade'
... dan templatnya sendiri seperti ini:
{{- range $index, $job := .Values.deploy_jobs }} --- apiVersion: batch/v1 kind: Job metadata: name: {{ $.Chart.Name }}-{{ $job.name }} annotations: "helm.sh/hook": {{ pluck $.Values.global.env $job.when | first | default $job.when._default }} "helm.sh/hook-weight": "1{{ $index }}" spec: activeDeadlineSeconds: {{ $job.activeDeadlineSeconds }} backoffLimit: 0 template: metadata: name: {{ $.Chart.Name }}-{{ $job.name }} spec: imagePullSecrets: - name: {{ required "$.Values.registry.secret_name required" $.Values.registry.secret_name }} initContainers: - name: wait-db image: alpine:3.6 ommand: ["/bin/sh", "-c", "while ! nc -z postgres 5432; do sleep 1; done;"] containers: - name: job command: {{ $job.command }} {{ tuple "backend" $ | include "werf_container_image" | indent 8 }} env: {{ tuple "backend" $ | include "werf_container_env" | indent 8 }} - name: DB_HOST value: postgres restartPolicy: Never {{- end }}
Pendekatan ini memungkinkan Anda untuk dengan cepat menambahkan perintah baru ke proses rilis dan membuat daftar perintah yang dapat dieksekusi lebih visual.
Kesimpulan
Artikel ini memberikan contoh templat yang memungkinkan Anda untuk menggambarkan operasi umum yang perlu Anda lakukan dalam proses merilis versi baru aplikasi. Meskipun mereka adalah hasil dari pengalaman dalam mengimplementasikan proses CI / CD di lusinan proyek, kami tidak bersikeras bahwa hanya ada satu solusi yang tepat untuk semua tugas. Jika contoh yang dijelaskan dalam artikel tidak mencakup kebutuhan proyek Anda, kami akan senang melihat situasi dalam komentar yang akan membantu menambah bahan ini.
Komentar dari pengembang werf:
Di masa depan, werf berencana untuk memperkenalkan tahap penggunaan sumber daya yang dapat dikonfigurasi pengguna. Dengan bantuan tahap-tahap seperti itu akan mungkin untuk menggambarkan kedua kasus dan tidak hanya.
PS
Baca juga di blog kami: