Buat paket untuk Kubernetes dengan Helm: struktur bagan dan templating



Kami berbicara tentang Helm dan bekerja dengannya "secara umum" di artikel sebelumnya . Sekarang mari kita mendekati latihan dari sisi lain - dari sudut pandang pencipta grafik (mis. Paket untuk Helm). Dan meskipun artikel ini berasal dari dunia eksploitasi, ternyata lebih mirip dengan materi tentang bahasa pemrograman - seperti nasib penulis grafik. Jadi, bagan adalah kumpulan file ...

File grafik dapat dibagi menjadi dua grup:

  1. File diperlukan untuk menghasilkan manifes sumber daya Kubernetes. Ini termasuk template dari direktori templates dan file dengan nilai (nilai default disimpan dalam values.yaml ). Juga di grup ini adalah file requirements.yaml dan direktori charts - semua ini digunakan untuk mengatur grafik bersarang.
  2. File yang menyertai berisi informasi yang mungkin berguna dalam menemukan grafik, mengenal mereka dan menggunakannya. Sebagian besar file dalam grup ini adalah opsional.

Informasi lebih lanjut tentang file dari kedua grup:

  • Chart.yaml - file dengan informasi tentang bagan;
  • LICENSE - file teks opsional dengan lisensi grafik;
  • README.md - file opsional dengan dokumentasi;
  • requirements.yaml - file opsional dengan daftar diagram dependensi;
  • values.yaml - file dengan nilai default untuk template;
  • charts/ - direktori opsional dengan grafik bersarang;
  • direktori templates/ - dengan templat manifest sumber daya Kubernetes;
  • templates/NOTES.txt - file teks opsional dengan catatan yang ditampilkan kepada pengguna selama instalasi dan pembaruan.

Untuk lebih memahami konten file-file ini, Anda bisa merujuk ke panduan pengembang bagan resmi atau mencari contoh yang relevan di repositori resmi .

Membuat grafik pada umumnya datang untuk mengatur set file yang dirancang dengan baik. Dan kesulitan utama dalam "desain" ini adalah penggunaan sistem template yang cukup canggih untuk mencapai hasil yang diinginkan. Untuk rendering manifest sumber daya Kubernetes, mesin templat Go standar digunakan , diperpanjang oleh fungsi Helm .

Pengingat : Pengembang Helm mengumumkan bahwa dalam versi utama proyek selanjutnya - Helm 3 - akan ada dukungan untuk skrip Lua, yang dapat digunakan secara bersamaan dengan templat Go. Saya tidak akan membahas hal ini secara lebih rinci - ini (dan perubahan lain dalam Helm 3) dapat dibaca di sini .

Sebagai contoh, inilah cara Helm 2 terlihat seperti template manifes Kubernet Deployment untuk blog WordPress dari artikel sebelumnya :

deployment.yaml
 apiVersion: extensions/v1beta1 kind: Deployment metadata: name: {{ template "fullname" . }} labels: app: {{ template "fullname" . }} chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" release: "{{ .Release.Name }}" heritage: "{{ .Release.Service }}" spec: replicas: {{ .Values.replicaCount }} template: metadata: labels: app: {{ template "fullname" . }} chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" release: "{{ .Release.Name }}" spec: {{- if .Values.image.pullSecrets }} imagePullSecrets: {{- range .Values.image.pullSecrets }} - name: {{ . }} {{- end}} {{- end }} containers: - name: {{ template "fullname" . }} image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy | quote }} env: - name: ALLOW_EMPTY_PASSWORD {{- if .Values.allowEmptyPassword }} value: "yes" {{- else }} value: "no" {{- end }} - name: MARIADB_HOST {{- if .Values.mariadb.enabled }} value: {{ template "mariadb.fullname" . }} {{- else }} value: {{ .Values.externalDatabase.host | quote }} {{- end }} - name: MARIADB_PORT_NUMBER {{- if .Values.mariadb.enabled }} value: "3306" {{- else }} value: {{ .Values.externalDatabase.port | quote }} {{- end }} - name: WORDPRESS_DATABASE_NAME {{- if .Values.mariadb.enabled }} value: {{ .Values.mariadb.db.name | quote }} {{- else }} value: {{ .Values.externalDatabase.database | quote }} {{- end }} - name: WORDPRESS_DATABASE_USER {{- if .Values.mariadb.enabled }} value: {{ .Values.mariadb.db.user | quote }} {{- else }} value: {{ .Values.externalDatabase.user | quote }} {{- end }} - name: WORDPRESS_DATABASE_PASSWORD valueFrom: secretKeyRef: {{- if .Values.mariadb.enabled }} name: {{ template "mariadb.fullname" . }} key: mariadb-password {{- else }} name: {{ printf "%s-%s" .Release.Name "externaldb" }} key: db-password {{- end }} - name: WORDPRESS_USERNAME value: {{ .Values.wordpressUsername | quote }} - name: WORDPRESS_PASSWORD valueFrom: secretKeyRef: name: {{ template "fullname" . }} key: wordpress-password - name: WORDPRESS_EMAIL value: {{ .Values.wordpressEmail | quote }} - name: WORDPRESS_FIRST_NAME value: {{ .Values.wordpressFirstName | quote }} - name: WORDPRESS_LAST_NAME value: {{ .Values.wordpressLastName | quote }} - name: WORDPRESS_BLOG_NAME value: {{ .Values.wordpressBlogName | quote }} - name: WORDPRESS_TABLE_PREFIX value: {{ .Values.wordpressTablePrefix | quote }} - name: SMTP_HOST value: {{ .Values.smtpHost | quote }} - name: SMTP_PORT value: {{ .Values.smtpPort | quote }} - name: SMTP_USER value: {{ .Values.smtpUser | quote }} - name: SMTP_PASSWORD valueFrom: secretKeyRef: name: {{ template "fullname" . }} key: smtp-password - name: SMTP_USERNAME value: {{ .Values.smtpUsername | quote }} - name: SMTP_PROTOCOL value: {{ .Values.smtpProtocol | quote }} ports: - name: http containerPort: 80 - name: https containerPort: 443 livenessProbe: httpGet: path: /wp-login.php {{- if not .Values.healthcheckHttps }} port: http {{- else }} port: https scheme: HTTPS {{- end }} {{ toYaml .Values.livenessProbe | indent 10 }} readinessProbe: httpGet: path: /wp-login.php {{- if not .Values.healthcheckHttps }} port: http {{- else }} port: https scheme: HTTPS {{- end }} {{ toYaml .Values.readinessProbe | indent 10 }} volumeMounts: - mountPath: /bitnami/apache name: wordpress-data subPath: apache - mountPath: /bitnami/wordpress name: wordpress-data subPath: wordpress - mountPath: /bitnami/php name: wordpress-data subPath: php resources: {{ toYaml .Values.resources | indent 10 }} volumes: - name: wordpress-data {{- if .Values.persistence.enabled }} persistentVolumeClaim: claimName: {{ .Values.persistence.existingClaim | default (include "fullname" .) }} {{- else }} emptyDir: {} {{ end }} {{- if .Values.nodeSelector }} nodeSelector: {{ toYaml .Values.nodeSelector | indent 8 }} {{- end -}} {{- with .Values.affinity }} affinity: {{ toYaml . | indent 8 }} {{- end }} {{- with .Values.tolerations }} tolerations: {{ toYaml . | indent 8 }} {{- end }} 

Sekarang - tentang prinsip dasar dan fitur standardisasi di Helm. Sebagian besar contoh di bawah ini diambil dari grafik repositori resmi .

Templating


Templat: {{ }}


Segala sesuatu yang terkait dengan templating dibungkus dengan kurung kurawal ganda. Teks di luar kurung keriting tetap tidak berubah selama rendering.

Nilai Konteks :.


Saat merender file atau sebagian (untuk informasi lebih lanjut tentang penggunaan kembali templat, lihat bagian artikel berikutnya) , nilai yang dapat diakses secara internal melalui variabel konteks - titik dilemparkan. Ketika diteruskan sebagai argumen ke struktur, titik tersebut digunakan untuk mengakses bidang dan metode struktur ini.

Nilai variabel berubah selama proses rendering tergantung pada konteks di mana ia digunakan. Sebagian besar pernyataan blok mengabaikan variabel konteks di dalam blok utama. Operator utama dan fitur-fiturnya akan dibahas di bawah ini, setelah berkenalan dengan struktur Helm dasar.

Struktur dasar Helm


Saat merender manifes, struktur dengan bidang berikut dilemparkan ke templat:

  • Bidang. .Values - untuk akses ke parameter yang ditentukan selama instalasi dan pembaruan rilis. Ini termasuk nilai opsi --set , --set-string dan --set-file , serta parameter file dengan nilai, file values.yaml dan file yang sesuai dengan nilai opsi - --values :

     containers: - name: main image: "{{ .Values.image }}:{{ .Values.imageTag }}" imagePullPolicy: {{ .Values.imagePullPolicy }} 
  • .Release - untuk menggunakan data rilis tentang peluncuran, pemasangan atau pembaruan, rilis nama, namespace, dan nilai beberapa bidang lainnya yang mungkin berguna saat membuat manifes:

     metadata: labels: heritage: "{{ .Release.Service }}" release: "{{ .Release.Name }}" subjects: - namespace: {{ .Release.Namespace }} 
  • .Chart - untuk mengakses informasi grafik . Kolom sesuai dengan isi file Chart.yaml :

     labels: chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" 
  • Struktur. .Files - untuk bekerja dengan file yang disimpan dalam direktori grafik ; struktur dan metode yang tersedia dapat ditemukan di sini . Contoh:

     data: openssl.conf: | {{ .Files.Get "config/openssl.conf" | indent 4 }} 

     data: {{ (.Files.Glob "files/docker-entrypoint-initdb.d/*").AsConfig | indent 2 }} 
  • .Capabilities - untuk mengakses informasi tentang cluster di mana .Capabilities dilakukan:

     {{- if .Capabilities.APIVersions.Has "apps/v1beta2" }} apiVersion: apps/v1beta2 {{- else }} apiVersion: extensions/v1beta1 {{- end }} 

     {{- if semverCompare "^1.9-0" .Capabilities.KubeVersion.GitVersion }} apiVersion: apps/v1 {{- else }} 

Operator


Kita mulai, tentu saja, dengan pernyataan if , else if dan else :

 {{- if .Values.agent.image.tag }} image: "{{ .Values.agent.image.repository }}:{{ .Values.agent.image.tag }}" {{- else }} image: "{{ .Values.agent.image.repository }}:v{{ .Chart.AppVersion }}" {{- end }} 

Operator range dirancang untuk bekerja dengan array dan peta. Jika array dilewatkan sebagai argumen dan berisi elemen, maka untuk setiap elemen blok dieksekusi secara berurutan (dalam hal ini, nilai di dalam blok menjadi tersedia melalui variabel konteks):

 {{- range .Values.ports }} - name: {{ .name }} port: {{ .containerPort }} targetPort: {{ .containerPort}} {{- else }} ... {{- end}} 

 {{ range .Values.tolerations -}} - {{ toYaml . | indent 8 | trim }} {{ end }} 

Untuk bekerja dengan peta, sintaks dengan variabel disediakan:

 {{- range $key, $value := .Values.credentials.secretContents }} {{ $key }}: {{ $value | b64enc | quote }} {{- end }} 

Perilaku serupa adalah with : operator: jika argumen yang dilewati ada, blok dieksekusi, dan variabel konteks di blok sesuai dengan nilai argumen. Sebagai contoh:

 {{- with .config }} config: {{- with .region }} region: {{ . }} {{- end }} {{- with .s3ForcePathStyle }} s3ForcePathStyle: {{ . }} {{- end }} {{- with .s3Url }} s3Url: {{ . }} {{- end }} {{- with .kmsKeyId }} kmsKeyId: {{ . }} {{- end }} {{- end }} 

Untuk menggunakan kembali templat, bundel dari define [name] dan template [name] [variable] dapat digunakan, di mana nilai yang dikirimkan tersedia melalui variabel konteks di blok define :

 apiVersion: v1 kind: ServiceAccount metadata: name: {{ template "kiam.serviceAccountName.agent" . }} ... {{- define "kiam.serviceAccountName.agent" -}} {{- if .Values.serviceAccounts.agent.create -}} {{ default (include "kiam.agent.fullname" .) .Values.serviceAccounts.agent.name }} {{- else -}} {{ default "default" .Values.serviceAccounts.agent.name }} {{- end -}} {{- end -}} 

Beberapa fitur yang perlu dipertimbangkan saat menggunakan define , atau, lebih sederhana, partial'ov:

  • Deklarasi parsial bersifat global dan dapat digunakan di semua file direktori templates .
  • Grafik utama dikompilasi bersama dengan grafik dependen, jadi jika ada dua nama parsial parsial dari jenis yang sama, yang dimuat terakhir akan digunakan. Saat menamai sebagian, biasanya menambahkan nama bagan untuk menghindari konflik seperti itu: define "chart_name.partial_name" .

Variabel: $


Selain bekerja dengan konteksnya, Anda dapat menyimpan, mengubah, dan menggunakan kembali data menggunakan variabel:

 {{ $provider := .Values.configuration.backupStorageProvider.name }} ... {{ if eq $provider "azure" }} envFrom: - secretRef: name: {{ template "ark.secretName" . }} {{ end }} 

Saat merender file atau sebagian, $ memiliki arti yang sama dengan titik. Tetapi tidak seperti variabel konteks (titik), nilai $ tidak berubah dalam konteks pernyataan blok , yang memungkinkan Anda untuk secara bersamaan bekerja dengan nilai konteks pernyataan blok dan struktur Helm dasar (atau nilai yang diteruskan ke parsial, jika kita berbicara tentang menggunakan $ inside partial'a) . Ilustrasi Perbedaan:

 context: {{ . }} dollar: {{ $ }} with: {{- with .Chart }} context: {{ . }} dollar: {{ $ }} {{- end }} template: {{- template "flant" .Chart -}} {{ define "flant" }} context: {{ . }} dollar: {{ $ }} with: {{- with .Name }} context: {{ . }} dollar: {{ $ }} {{- end }} {{- end -}} 

Sebagai hasil dari pemrosesan templat ini, yang berikut akan berubah (untuk kejelasan, dalam output struktur diganti dengan nama-semu yang sesuai):

 context: #  helm dollar: #  helm with: context: #.Chart dollar: #  helm template: context: #.Chart dollar: #.Chart with: context: habr dollar: #.Chart 

Dan di sini adalah contoh nyata menggunakan fitur ini:

 {{- if .Values.ingress.enabled -}} {{- range .Values.ingress.hosts }} apiVersion: extensions/v1beta1 kind: Ingress metadata: name: {{ template "nats.fullname" $ }}-monitoring labels: app: "{{ template "nats.name" $ }}" chart: "{{ template "nats.chart" $ }}" release: {{ $.Release.Name | quote }} heritage: {{ $.Release.Service | quote }} annotations: {{- if .tls }} ingress.kubernetes.io/secure-backends: "true" {{- end }} {{- range $key, $value := .annotations }} {{ $key }}: {{ $value | quote }} {{- end }} spec: rules: - host: {{ .name }} http: paths: - path: {{ default "/" .path }} backend: serviceName: {{ template "nats.fullname" $ }}-monitoring servicePort: monitoring {{- if .tls }} tls: - hosts: - {{ .name }} secretName: {{ .tlsSecret }} {{- end }} --- {{- end }} {{- end }} 

Lekukan


Saat mengembangkan templat, margin tambahan mungkin tersisa: spasi, tab, umpan baris. Dengan mereka, file hanya terlihat lebih mudah dibaca. Anda dapat mengabaikannya, atau menggunakan sintaks khusus untuk menghapus lekukan di sekitar pola yang digunakan:

  • {{- variable }} memotong spasi sebelumnya;
  • {{ variable -}} memotong spasi berikutnya;
  • {{- variable -}} keduanya opsi.

Contoh file, yang pengolahannya akan menjadi garis habr flant helm :

 habr {{- " flant " -}} helm 

Fungsi bawaan


Semua fungsi yang dibangun dalam templat dapat ditemukan di tautan berikut . Di sini saya hanya akan bercerita tentang beberapa di antaranya.

Fungsi index dirancang untuk mengakses elemen array atau peta:

 definitions.json: | { "users": [ { "name": "{{ index .Values "rabbitmq-ha" "rabbitmqUsername" }}", "password": "{{ index .Values "rabbitmq-ha" "rabbitmqPassword" }}", "tags": "administrator" } ] } 

Fungsi ini mengambil sejumlah argumen sembarang, yang memungkinkan Anda untuk bekerja dengan elemen bersarang:

$map["key1"]["key2"]["key3"] => index $map "key1" "key2" "key3"

Sebagai contoh:

 httpGet: {{- if (index .Values "pushgateway" "extraArgs" "web.route-prefix") }} path: /{{ index .Values "pushgateway" "extraArgs" "web.route-prefix" }}/#/status {{- end }} 

Operasi Boolean diimplementasikan dalam mesin template sebagai fungsi (dan bukan sebagai operator). Semua argumen untuk mereka dievaluasi ketika melewati:

 {{ if and (index .Values field) (eq (len .Values.field) 10) }} ... {{ end }} 

Jika tidak ada bidang field rendering templat akan gagal ( error calling len: len of untyped nil ): kondisi kedua dicentang, meskipun faktanya yang pertama belum terpenuhi. Sebaiknya buat catatan, dan pecahkan pertanyaan seperti itu dengan membagi menjadi beberapa cek:

 {{ if index . field }} {{ if eq (len .field) 10 }} ... {{ end }} {{ end }} 

Pipeline adalah fitur unik templat Go yang memungkinkan Anda mendeklarasikan ekspresi yang dieksekusi seperti pipeline di shell. Secara formal, pipeline adalah rantai perintah yang dipisahkan oleh simbol | . Perintah dapat berupa nilai sederhana atau panggilan fungsi . Hasil dari setiap perintah dilewatkan sebagai argumen terakhir ke perintah berikutnya , dan hasil dari perintah terakhir dalam pipa adalah nilai dari keseluruhan pipa. Contoh:

 data: openssl.conf: | {{ .Files.Get "config/openssl.conf" | indent 4 }} 

 data: db-password: {{ .Values.externalDatabase.password | b64enc | quote }} 

Fungsi tambahan


Sprig adalah pustaka 70 fitur yang berguna untuk menyelesaikan berbagai tugas. Untuk alasan keamanan, Helm mengecualikan fungsi env dan expandenv yang menyediakan akses ke variabel lingkungan Tiller.

Fungsi include , seperti fungsi template standar, digunakan untuk menggunakan kembali templat. Tidak seperti template , fungsi tersebut dapat digunakan dalam pipa, mis. meneruskan hasilnya ke fungsi lain:

 metadata: labels: {{ include "labels.standard" . | indent 4 }} {{- define "labels.standard" -}} app: {{ include "hlf-couchdb.name" . }} heritage: {{ .Release.Service | quote }} release: {{ .Release.Name | quote }} chart: {{ include "hlf-couchdb.chart" . }} {{- end -}} 

Fungsi yang required memungkinkan pengembang untuk mendeklarasikan nilai wajib yang diperlukan untuk merender templat: jika ada suatu nilai, itu digunakan saat merender templat, jika tidak, rendering berakhir dengan pesan kesalahan yang ditunjukkan oleh pengembang:

 sftp-user: {{ required "Please specify the SFTP user name at .Values.sftp.user" .Values.sftp.user | b64enc | quote }} sftp-password: {{ required "Please specify the SFTP user password at .Values.sftp.password" .Values.sftp.password | b64enc | quote }} {{- end }} {{- if .Values.svn.enabled }} svn-user: {{ required "Please specify the SVN user name at .Values.svn.user" .Values.svn.user | b64enc | quote }} svn-password: {{ required "Please specify the SVN user password at .Values.svn.password" .Values.svn.password | b64enc | quote }} {{- end }} {{- if .Values.webdav.enabled }} webdav-user: {{ required "Please specify the WebDAV user name at .Values.webdav.user" .Values.webdav.user | b64enc | quote }} webdav-password: {{ required "Please specify the WebDAV user password at .Values.webdav.password" .Values.webdav.password | b64enc | quote }} {{- end }} 

Fungsi tpl memungkinkan Anda untuk membuat string sebagai templat. Tidak seperti template dan include , fungsi ini memungkinkan Anda untuk mengeksekusi templat yang dikirimkan dalam variabel, serta membuat templat yang disimpan tidak hanya di direktori templates . Seperti apa bentuknya?

Menjalankan templat dari variabel:

 containers: {{- with .Values.keycloak.extraContainers }} {{ tpl . $ | indent 2 }} {{- end }} 

... dan dalam values.yaml kita memiliki nilai berikut:

 keycloak: extraContainers: | - name: cloudsql-proxy image: gcr.io/cloudsql-docker/gce-proxy:1.11 command: - /cloud_sql_proxy args: - -instances={{ .Values.cloudsql.project }}:{{ .Values.cloudsql.region }}:{{ .Values.cloudsql.instance }}=tcp:5432 - -credential_file=/secrets/cloudsql/credentials.json volumeMounts: - name: cloudsql-creds mountPath: /secrets/cloudsql readOnly: true 

Render file yang disimpan di luar direktori templates :

 apiVersion: batch/v1 kind: Job metadata: name: {{ template "mysqldump.fullname" . }} labels: app: {{ template "mysqldump.name" . }} chart: {{ template "mysqldump.chart" . }} release: "{{ .Release.Name }}" heritage: "{{ .Release.Service }}" spec: backoffLimit: 1 template: {{ $file := .Files.Get "files/job.tpl" }} {{ tpl $file . | indent 4 }} 

... pada bagan, di sepanjang files/job.tpl path files/job.tpl , ada templat berikut:

 spec: containers: - name: xtrabackup image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy | quote }} command: ["/bin/bash", "/scripts/backup.sh"] envFrom: - configMapRef: name: "{{ template "mysqldump.fullname" . }}" - secretRef: name: "{{ template "mysqldump.fullname" . }}" volumeMounts: - name: backups mountPath: /backup - name: xtrabackup-script mountPath: /scripts restartPolicy: Never volumes: - name: backups {{- if .Values.persistentVolumeClaim }} persistentVolumeClaim: claimName: {{ .Values.persistentVolumeClaim }} {{- else -}} {{- if .Values.persistence.enabled }} persistentVolumeClaim: claimName: {{ template "mysqldump.fullname" . }} {{- else }} emptyDir: {} {{- end }} {{- end }} - name: xtrabackup-script configMap: name: {{ template "mysqldump.fullname" . }}-script 

Di sinilah pengantar dasar-dasar standardisasi di Helm berakhir ...

Kesimpulan


Artikel ini menjelaskan struktur grafik Helm dan memeriksa secara rinci kesulitan utama dalam pembuatannya - template: prinsip dasar, sintaksis, fungsi dan operator Templat-gulir, fungsi tambahan.

Bagaimana cara mulai bekerja dengan semua ini? Karena Helm sudah merupakan keseluruhan ekosistem, Anda selalu dapat melihat contoh grafik untuk paket serupa. Misalnya, jika Anda ingin mengemas antrian pesan baru, lihat grafik publik RabbitMQ . Tentu saja, tidak ada yang menjanjikan Anda implementasi ideal dalam paket yang ada, tetapi mereka sempurna sebagai titik awal. Selebihnya dilengkapi dengan latihan, di mana helm template dan perintah debug helm lint akan membantu Anda, serta memulai instalasi dengan opsi --dry-run .

Untuk pemahaman yang lebih luas tentang perkembangan grafik Helm, praktik terbaik dan teknologi yang digunakan, saya sarankan Anda membiasakan diri dengan materi di tautan berikut (semua dalam bahasa Inggris):


Dan di akhir materi Helm berikutnya, saya melampirkan survei yang akan membantu untuk lebih memahami apa artikel lain tentang Helm yang menunggu pembaca Habr (atau tidak menunggu?). Terima kasih atas perhatian anda!

PS


Baca juga di blog kami:

Source: https://habr.com/ru/post/id423239/


All Articles