werf - alat kami untuk CI / CD di Kubernetes (ikhtisar dan laporan video)

27 Mei di aula utama konferensi DevOpsConf 2019, yang diadakan sebagai bagian dari festival RIT++ 2019, sebagai bagian dari bagian “Pengiriman Berkelanjutan”, sebuah laporan diberikan “werf - alat kami untuk CI/CD di Kubernetes”. Ini berbicara tentang hal itu masalah dan tantangan yang dihadapi semua orang saat menerapkan Kubernetes, serta tentang nuansa yang mungkin tidak langsung terlihat. Menganalisis solusi yang mungkin, kami menunjukkan bagaimana hal ini diterapkan dalam alat Sumber Terbuka wer.

Sejak presentasi, utilitas kami (sebelumnya dikenal sebagai dapp) telah mencapai tonggak sejarah 1000 bintang di GitHub — kami berharap komunitas penggunanya yang berkembang akan membuat hidup lebih mudah bagi banyak teknisi DevOps.

werf - alat kami untuk CI / CD di Kubernetes (ikhtisar dan laporan video)

Jadi, mari kita perkenalkan video laporan tersebut (~47 menit, jauh lebih informatif daripada artikel) dan ekstrak utama dalam bentuk teks. Pergi!

Mengirimkan kode ke Kubernetes

Pembicaraannya tidak lagi tentang werf, tetapi tentang CI/CD di Kubernetes, yang menyiratkan bahwa perangkat lunak kami dikemas dalam container Docker (Saya membicarakan hal ini di laporan tahun 2016), dan K8 akan digunakan untuk menjalankannya dalam produksi (lebih lanjut tentang ini di 2017 tahun).

Seperti apa pengiriman di Kubernetes?

  • Ada repositori Git dengan kode dan instruksi untuk membangunnya. Aplikasi ini dibangun ke dalam image Docker dan dipublikasikan di Docker Registry.
  • Repositori yang sama juga berisi instruksi tentang cara menyebarkan dan menjalankan aplikasi. Pada tahap penerapan, instruksi ini dikirim ke Kubernetes, yang menerima image yang diinginkan dari registri dan meluncurkannya.
  • Ditambah lagi, biasanya ada tes. Beberapa di antaranya dapat dilakukan saat memublikasikan gambar. Anda juga dapat (mengikuti instruksi yang sama) menyebarkan salinan aplikasi (dalam namespace K8 terpisah atau cluster terpisah) dan menjalankan pengujian di sana.
  • Terakhir, Anda memerlukan sistem CI yang menerima kejadian dari Git (atau klik tombol) dan memanggil semua tahapan yang ditentukan: build, publikasikan, terapkan, uji.

werf - alat kami untuk CI / CD di Kubernetes (ikhtisar dan laporan video)

Ada beberapa catatan penting di sini:

  1. Karena kami memiliki infrastruktur yang tidak dapat diubah (infrastruktur abadi), gambar aplikasi yang digunakan di semua tahapan (pementasan, produksi, dll.), pasti ada satu. Saya membicarakan hal ini secara lebih rinci dan dengan contoh. di sini.
  2. Karena kami mengikuti pendekatan infrastruktur sebagai kode (IaC), kode aplikasi, instruksi untuk merakit dan meluncurkannya seharusnya persis dalam satu repositori. Untuk informasi lebih lanjut tentang ini, lihat laporan yang sama.
  3. Rantai pengiriman (pengiriman) kita biasanya melihatnya seperti ini: aplikasi telah dirakit, diuji, dirilis (tahap rilis) dan itu saja - pengiriman telah dilakukan. Namun kenyataannya, pengguna mendapatkan apa yang Anda luncurkan, tidak lalu saat Anda mengirimkannya ke produksi, dan saat dia bisa pergi ke sana dan produksi ini berhasil. Jadi saya yakin rantai pengiriman berakhir hanya pada tahap operasional (Lari), atau lebih tepatnya, bahkan pada saat kode tersebut dihapus dari produksi (menggantinya dengan yang baru).

Mari kita kembali ke skema pengiriman di Kubernetes di atas: skema ini tidak hanya ditemukan oleh kami, tetapi juga oleh semua orang yang menangani masalah ini. Faktanya, pola ini sekarang disebut GitOps (Anda dapat membaca lebih lanjut tentang istilah dan ide di baliknya di sini). Mari kita lihat tahapan skemanya.

Tahap membangun

Tampaknya Anda dapat berbicara tentang membuat image Docker pada tahun 2019, ketika semua orang tahu cara menulis Dockerfiles dan menjalankannya docker build?.. Berikut nuansa yang ingin saya perhatikan:

  1. Berat gambar penting, jadi gunakanlah multi-tahapuntuk meninggalkan dalam gambar hanya aplikasi yang benar-benar diperlukan untuk pengoperasian.
  2. Jumlah lapisan harus diminimalkan dengan menggabungkan rantai RUN-perintah menurut artinya.
  3. Namun, hal ini menambah masalah melakukan debug, karena ketika perakitan macet, Anda harus menemukan perintah yang tepat dari rantai yang menyebabkan masalah.
  4. Kecepatan perakitan penting karena kami ingin segera meluncurkan perubahan dan melihat hasilnya. Misalnya, Anda tidak ingin membangun kembali dependensi di pustaka bahasa setiap kali Anda membuat aplikasi.
  5. Seringkali dari satu repositori Git yang Anda butuhkan banyak gambar, yang dapat diselesaikan dengan sekumpulan Dockerfiles (atau bernama tahapan dalam satu file) dan skrip Bash dengan perakitan berurutannya.

Ini hanyalah puncak gunung es yang dihadapi semua orang. Namun ada masalah lain, khususnya:

  1. Seringkali pada tahap perakitan kita membutuhkan sesuatu gunung (misalnya, cache hasil perintah seperti apt di direktori pihak ketiga).
  2. Kami ingin Mungkin alih-alih menulis di shell.
  3. Kami ingin membangun tanpa Docker (mengapa kita memerlukan mesin virtual tambahan di mana kita perlu mengkonfigurasi semuanya untuk ini, padahal kita sudah memiliki cluster Kubernetes di mana kita dapat menjalankan container?).
  4. Perakitan paralel, yang dapat dipahami dengan cara berbeda: perintah berbeda dari Dockerfile (jika multi-tahap digunakan), beberapa komit dari repositori yang sama, beberapa Dockerfiles.
  5. Perakitan terdistribusi: Kami ingin mengumpulkan barang-barang di pod yang bersifat "sementara" karena cache mereka hilang, yang berarti perlu disimpan di suatu tempat secara terpisah.
  6. Akhirnya, saya sebutkan puncak keinginan sihir otomatis: Idealnya pergi ke repositori, ketikkan beberapa perintah dan dapatkan gambar yang sudah jadi, dirangkai dengan pemahaman tentang bagaimana dan apa yang harus dilakukan dengan benar. Namun, saya pribadi tidak yakin bahwa semua nuansa dapat diramalkan dengan cara ini.

Dan inilah proyek-proyeknya:

  • moby/buildkit — pembuat dari Docker Inc (sudah terintegrasi ke dalam versi Docker saat ini), yang mencoba menyelesaikan semua masalah ini;
  • kaniko — pembuat dari Google yang memungkinkan Anda membangun tanpa Docker;
  • Buildpacks.io — Upaya CNCF untuk membuat keajaiban otomatis dan, khususnya, solusi menarik dengan rebase untuk lapisan;
  • dan banyak utilitas lainnya, seperti bangunan, alat asli/img...

...dan lihat berapa banyak bintang yang mereka miliki di GitHub. Artinya, di satu sisi, docker build ada dan dapat melakukan sesuatu, tetapi kenyataannya masalahnya belum terselesaikan sepenuhnya - buktinya adalah pengembangan paralel dari kolektor alternatif, yang masing-masing memecahkan beberapa masalah.

Perakitan di werf

Jadi kita harus melakukannya wer (sebelumnya terkenal seperti dapp) — Utilitas open source dari perusahaan Flant, yang telah kami buat selama bertahun-tahun. Semuanya dimulai 5 tahun yang lalu dengan skrip Bash yang mengoptimalkan perakitan Dockerfiles, dan selama 3 tahun terakhir pengembangan penuh telah dilakukan dalam kerangka proyek yang sama dengan repositori Gitnya sendiri (pertama di Ruby, lalu menulis ulang to Go, dan pada saat yang sama berganti nama). Masalah perakitan apa yang diselesaikan di werf?

werf - alat kami untuk CI / CD di Kubernetes (ikhtisar dan laporan video)

Masalah yang diberi warna biru telah dilaksanakan, pembangunan paralel dilakukan dalam host yang sama, dan masalah yang diberi tanda warna kuning direncanakan akan selesai pada akhir musim panas.

Tahap publikasi di registri (publish)

Kami menelepon docker push... - apa yang sulit dalam mengunggah gambar ke registri? Dan kemudian muncul pertanyaan: “Tag apa yang harus saya masukkan pada gambar?” Itu muncul karena alasan yang kita miliki aliran Git (atau strategi Git lainnya) dan Kubernetes, dan industri ini berusaha memastikan bahwa apa yang terjadi di Kubernetes mengikuti apa yang terjadi di Git. Bagaimanapun, Git adalah satu-satunya sumber kebenaran kami.

Apa yang begitu sulit tentang ini? Pastikan reproduktifitas: dari komit di Git, yang sifatnya tidak dapat diubah (kekal), ke image Docker, yang harus tetap sama.

Ini juga penting bagi kami menentukan asal, karena kami ingin memahami dari komit mana aplikasi yang berjalan di Kubernetes dibuat (maka kami dapat melakukan perbedaan dan hal serupa).

Strategi Penandaan

Yang pertama sederhana tag git. Kami memiliki registri dengan gambar yang diberi tag sebagai 1.0. Kubernetes memiliki panggung dan produksi, tempat gambar ini diunggah. Di Git kami membuat komitmen dan pada titik tertentu kami memberi tag 2.0. Kami mengumpulkannya sesuai dengan instruksi dari repositori dan menempatkannya di registri dengan tag 2.0. Kami meluncurkannya ke panggung dan, jika semuanya baik-baik saja, maka ke produksi.

werf - alat kami untuk CI / CD di Kubernetes (ikhtisar dan laporan video)

Masalah dengan pendekatan ini adalah pertama-tama kami memasang tag, baru kemudian menguji dan meluncurkannya. Mengapa? Pertama, ini tidak masuk akal: kami mengeluarkan versi perangkat lunak yang bahkan belum kami uji (kami tidak dapat melakukan sebaliknya, karena untuk memeriksanya, kami perlu memberi tag). Kedua, jalur ini tidak kompatibel dengan Gitflow.

Opsi kedua - git komit + tag. Cabang master memiliki tag 1.0; untuk itu di registri - gambar disebarkan ke produksi. Selain itu, cluster Kubernetes memiliki pratinjau dan staging kontur. Selanjutnya kita ikuti Gitflow: di cabang utama untuk pengembangan (develop) kami membuat fitur baru, menghasilkan komit dengan pengidentifikasi #c1. Kami mengumpulkannya dan mempublikasikannya di registri menggunakan pengidentifikasi ini (#c1). Dengan pengenal yang sama, kami meluncurkan pratinjau. Kami melakukan hal yang sama dengan komitmen #c2 и #c3.

Ketika kami menyadari bahwa ada cukup fitur, kami mulai menstabilkan semuanya. Buat cabang di Git release_1.1 (di pangkalan #c3 dari develop). Rilis ini tidak perlu dikumpulkan, karena... ini telah dilakukan pada langkah sebelumnya. Oleh karena itu, kita cukup meluncurkannya ke pementasan. Kami memperbaiki bug #c4 dan juga diluncurkan ke pementasan. Pada saat yang sama, pembangunan sedang berlangsung develop, tempat perubahan diambil secara berkala release_1.1. Pada titik tertentu, kami mendapatkan komit yang dikompilasi dan diunggah ke staging, yang mana kami senang (#c25).

Kemudian kami menggabungkan (dengan fast-forward) cabang rilis (release_1.1) di master. Kami memberi tag dengan versi baru pada komit ini (1.1). Namun gambar ini sudah terkumpul di registry, jadi agar tidak terkumpul lagi, kita cukup menambahkan tag kedua pada gambar yang sudah ada (sekarang sudah ada tag di registry #c25 и 1.1). Setelah itu, kami meluncurkannya ke produksi.

Ada kekurangannya hanya satu gambar yang diunggah ke pementasan (#c25), dan dalam produksinya agak berbeda (1.1), tapi kita tahu bahwa “secara fisik” ini adalah gambar yang sama dari registri.

werf - alat kami untuk CI / CD di Kubernetes (ikhtisar dan laporan video)

Kerugian sebenarnya adalah tidak ada dukungan untuk komitmen gabungan, Anda harus melakukan fast-forward.

Kita bisa melangkah lebih jauh dan melakukan sebuah trik... Mari kita lihat contoh Dockerfile sederhana:

FROM ruby:2.3 as assets
RUN mkdir -p /app
WORKDIR /app
COPY . ./
RUN gem install bundler && bundle install
RUN bundle exec rake assets:precompile
CMD bundle exec puma -C config/puma.rb

FROM nginx:alpine
COPY --from=assets /app/public /usr/share/nginx/www/public

Mari buat file darinya sesuai dengan prinsip berikut:

  • SHA256 dari pengidentifikasi gambar yang digunakan (ruby:2.3 и nginx:alpine), yang merupakan checksum dari isinya;
  • semua tim (RUN, CMD dan seterusnya.);
  • SHA256 dari file yang ditambahkan.

... dan ambil checksum (sekali lagi SHA256) dari file tersebut. Ini tanda tangan segala sesuatu yang mendefinisikan konten image Docker.

werf - alat kami untuk CI / CD di Kubernetes (ikhtisar dan laporan video)

Mari kita kembali ke diagram dan alih-alih melakukan, kami akan menggunakan tanda tangan tersebut, yaitu. menandai gambar dengan tanda tangan.

werf - alat kami untuk CI / CD di Kubernetes (ikhtisar dan laporan video)

Sekarang, ketika diperlukan, misalnya, untuk menggabungkan perubahan dari rilis ke master, kita dapat melakukan komit penggabungan yang sebenarnya: perubahan tersebut akan memiliki pengidentifikasi yang berbeda, tetapi tanda tangan yang sama. Dengan pengenal yang sama kami akan meluncurkan gambar tersebut ke produksi.

Kerugiannya adalah sekarang tidak mungkin untuk menentukan jenis komitmen apa yang didorong ke produksi - checksum hanya bekerja dalam satu arah. Masalah ini diselesaikan dengan lapisan tambahan dengan metadata - saya akan memberi tahu Anda lebih banyak nanti.

Menandai di werf

Di werf kami melangkah lebih jauh dan bersiap untuk melakukan pembangunan terdistribusi dengan cache yang tidak disimpan di satu mesin... Jadi, kami sedang membangun dua jenis gambar Docker, kami menyebutnya tahap и gambar.

Repositori werf Git menyimpan instruksi khusus build yang menjelaskan berbagai tahapan build (sebelumInstal, install, sebelumPengaturan, penyiapan). Kami mengumpulkan gambar tahap pertama dengan tanda tangan yang ditentukan sebagai checksum dari langkah pertama. Kemudian kita tambahkan kode sumbernya, untuk gambar panggung baru kita hitung checksumnya... Operasi ini diulangi untuk semua tahapan, sebagai hasilnya kita mendapatkan satu set gambar panggung. Kemudian kita membuat gambar akhir yang juga berisi metadata tentang asalnya. Dan kami menandai gambar ini dengan cara yang berbeda (detailnya nanti).

werf - alat kami untuk CI / CD di Kubernetes (ikhtisar dan laporan video)

Misalkan setelah ini muncul komit baru yang hanya kode aplikasinya yang diubah. Apa yang akan terjadi? Untuk perubahan kode, patch akan dibuat dan gambar panggung baru akan disiapkan. Tanda tangannya akan ditentukan sebagai checksum dari gambar panggung lama dan tambalan baru. Gambar akhir baru akan terbentuk dari gambar ini. Perilaku serupa akan terjadi dengan perubahan pada tahapan lainnya.

Jadi, gambar panggung adalah cache yang dapat disimpan secara terdistribusi, dan gambar yang sudah dibuat darinya diunggah ke Docker Registry.

werf - alat kami untuk CI / CD di Kubernetes (ikhtisar dan laporan video)

Membersihkan registri

Kami tidak berbicara tentang menghapus lapisan yang tetap menggantung setelah tag dihapus - ini adalah fitur standar dari Docker Registry itu sendiri. Kita berbicara tentang situasi ketika banyak tag Docker terakumulasi dan kami memahami bahwa kami tidak lagi memerlukan beberapa di antaranya, tetapi tag tersebut memakan ruang (dan/atau kami membayarnya).

Apa strategi pembersihannya?

  1. Anda tidak bisa berbuat apa-apa jangan bersihkan. Terkadang lebih mudah membayar sedikit untuk ruang ekstra daripada mengurai banyak tag. Tapi ini hanya berfungsi sampai titik tertentu.
  2. Reset penuh. Jika Anda menghapus semua image dan hanya membangun kembali image saat ini di sistem CI, masalah mungkin timbul. Jika container dimulai ulang dalam produksi, image baru akan dimuat untuk container tersebut - image yang belum diuji oleh siapa pun. Hal ini menghilangkan gagasan tentang infrastruktur yang tidak dapat diubah.
  3. Biru hijau. Satu registri mulai meluap - kami mengunggah gambar ke registri lainnya. Masalah yang sama seperti pada cara sebelumnya: pada titik manakah Anda dapat menghapus registri yang mulai meluap?
  4. Oleh waktu. Hapus semua gambar yang lebih lama dari 1 bulan? Tapi pasti akan ada layanan yang sebulan belum update...
  5. Secara manual menentukan apa yang sudah bisa dihapus.

Ada dua opsi yang benar-benar layak: jangan bersihkan atau kombinasikan biru-hijau + secara manual. Dalam kasus terakhir, kita berbicara tentang hal berikut: ketika Anda memahami bahwa sudah waktunya untuk membersihkan registri, Anda membuat yang baru dan menambahkan semua gambar baru ke dalamnya selama, misalnya, sebulan. Dan setelah sebulan, lihat pod mana di Kubernetes yang masih menggunakan registry lama, dan transfer juga ke registry baru.

Apa yang telah kita capai wer? Kami mengumpulkan:

  1. Git head: semua tag, semua cabang - dengan asumsi bahwa kita memerlukan semua yang diberi tag di Git pada gambar (dan jika tidak, maka kita perlu menghapusnya di Git itu sendiri);
  2. semua pod yang saat ini disalurkan ke Kubernetes;
  3. ReplicaSet lama (yang baru saja dirilis), dan kami juga berencana memindai rilis Helm dan memilih gambar terbaru di sana.

... dan buat daftar putih dari kumpulan ini - daftar gambar yang tidak akan kami hapus. Kami membersihkan semuanya, setelah itu kami menemukan gambar panggung yatim piatu dan menghapusnya juga.

Tahap penerapan

Deklaratif yang andal

Poin pertama yang ingin saya perhatikan dalam penerapan adalah peluncuran konfigurasi sumber daya yang diperbarui, yang dideklarasikan secara deklaratif. Dokumen YAML asli yang menjelaskan sumber daya Kubernetes selalu sangat berbeda dari hasil yang sebenarnya berjalan di cluster. Karena Kubernetes menambahkan konfigurasinya:

  1. pengidentifikasi;
  2. informasi layanan;
  3. banyak nilai default;
  4. bagian dengan status saat ini;
  5. perubahan yang dilakukan sebagai bagian dari webhook penerimaan;
  6. hasil kerja berbagai pengontrol (dan penjadwal).

Oleh karena itu, ketika konfigurasi sumber daya baru muncul (yang baru), kita tidak bisa begitu saja mengambil dan menimpa konfigurasi “langsung” saat ini dengannya (hidup). Untuk melakukan ini kita harus membandingkan yang baru dengan konfigurasi yang terakhir diterapkan (terakhir diterapkan) dan berguling ke atas hidup tambalan yang diterima.

Pendekatan ini disebut penggabungan 2 arah. Ini digunakan, misalnya, di Helm.

Ada juga penggabungan 3 arah, yang berbeda dalam hal itu:

  • perbandingan terakhir diterapkan и yang baru, kami melihat apa yang telah dihapus;
  • perbandingan yang baru и hidup, kita melihat apa yang ditambahkan atau diubah;
  • patch yang dijumlahkan diterapkan hidup.

Kami menerapkan 1000+ aplikasi dengan Helm, jadi kami benar-benar hidup dengan penggabungan 2 arah. Namun, ada sejumlah masalah yang telah kami selesaikan dengan tambalan kami, yang membantu Helm bekerja secara normal.

Status peluncuran sebenarnya

Setelah sistem CI kami menghasilkan konfigurasi baru untuk Kubernetes berdasarkan peristiwa berikutnya, sistem tersebut mengirimkannya untuk digunakan (menerapkan) ke cluster - menggunakan Helm atau kubectl apply. Selanjutnya, penggabungan N-way yang telah dijelaskan terjadi, yang mana Kubernetes API merespons dengan menyetujui sistem CI, dan juga kepada penggunanya.

werf - alat kami untuk CI / CD di Kubernetes (ikhtisar dan laporan video)

Namun, ada masalah besar: bagaimanapun juga aplikasi yang berhasil tidak berarti peluncurannya berhasil. Jika Kubernetes memahami perubahan apa yang perlu diterapkan dan menerapkannya, kita masih belum tahu apa hasilnya. Misalnya, memperbarui dan memulai ulang pod di frontend mungkin berhasil, tetapi tidak di backend, dan kita akan mendapatkan versi berbeda dari image aplikasi yang sedang berjalan.

Untuk melakukan semuanya dengan benar, skema ini memerlukan tautan tambahan - pelacak khusus yang akan menerima informasi status dari API Kubernetes dan mengirimkannya untuk analisis lebih lanjut tentang keadaan sebenarnya. Kami membuat perpustakaan Open Source di Go - cubedog (lihat pengumumannya di sini), yang memecahkan masalah ini dan dibangun ke dalam werf.

Perilaku pelacak ini di tingkat werf dikonfigurasikan menggunakan anotasi yang ditempatkan pada Deployment atau StatefulSets. Anotasi utama - fail-mode - memahami arti berikut:

  • IgnoreAndContinueDeployProcess — kami mengabaikan masalah dalam meluncurkan komponen ini dan melanjutkan penerapannya;
  • FailWholeDeployProcessImmediately — kesalahan pada komponen ini menghentikan proses penerapan;
  • HopeUntilEndOfDeployProcess — kami berharap komponen ini akan berfungsi pada akhir penerapan.

Misalnya, kombinasi sumber daya dan nilai anotasi fail-mode:

werf - alat kami untuk CI / CD di Kubernetes (ikhtisar dan laporan video)

Saat kami menerapkan untuk pertama kalinya, database (MongoDB) mungkin belum siap - Penerapan akan gagal. Namun Anda dapat menunggu hingga proses tersebut dimulai, dan penerapannya akan tetap dilakukan.

Ada dua anotasi lagi untuk kubedog di werf:

  • failures-allowed-per-replica — jumlah jatuh yang diperbolehkan untuk setiap replika;
  • show-logs-until — mengatur momen hingga werf menampilkan (di stdout) log dari semua pod yang diluncurkan. Standarnya adalah PodIsReady (untuk mengabaikan pesan yang mungkin tidak kita inginkan ketika lalu lintas mulai masuk ke pod), tetapi nilainya juga valid: ControllerIsReady и EndOfDeploy.

Apa lagi yang kita inginkan dari penerapan?

Selain dua poin yang telah dijelaskan, kami ingin:

  • untuk melihat log - dan hanya yang diperlukan, dan tidak semuanya;
  • melacak kemajuan, karena jika pekerjaan terhenti “diam-diam” selama beberapa menit, penting untuk memahami apa yang terjadi di sana;
  • untuk memiliki pengembalian otomatis jika terjadi kesalahan (dan oleh karena itu penting untuk mengetahui status penerapan yang sebenarnya). Peluncurannya harus bersifat atomik: bisa berlanjut hingga akhir, atau semuanya kembali ke keadaan sebelumnya.

Hasil

Bagi kami sebagai perusahaan, untuk menerapkan semua nuansa yang dijelaskan pada berbagai tahap pengiriman (membangun, menerbitkan, menerapkan), sistem dan utilitas CI sudah cukup. wer.

Alih-alih kesimpulan:

werf - alat kami untuk CI / CD di Kubernetes (ikhtisar dan laporan video)

Dengan bantuan werf, kami telah membuat kemajuan yang baik dalam memecahkan sejumlah besar masalah bagi para insinyur DevOps dan akan senang jika komunitas luas setidaknya mencoba utilitas ini dalam tindakan. Akan lebih mudah untuk mencapai hasil yang baik bersama-sama.

Video dan slide

Video dari pertunjukan (~47 menit):

Penyajian laporan:

PS

Laporan lain tentang Kubernetes di blog kami:

Sumber: www.habr.com

Tambah komentar