Bagaimana untuk memulakan ujian Ansible, refactor projek dalam setahun dan tidak menjadi gila

Bagaimana untuk memulakan ujian Ansible, refactor projek dalam setahun dan tidak menjadi gila

Ini transkripnya persembahan pada DevOps-40 2020-03-18:

Bermula dari komit kedua, sebarang kod menjadi warisan, kerana idea awal mula menyimpang dari realiti yang keras. Ini tidak baik dan tidak buruk, ia adalah pemberian yang sukar untuk dipertikaikan dan mesti dihayati. Sebahagian daripada proses ini ialah pemfaktoran semula. Infrastruktur Pemfaktoran Semula sebagai Kod. Biarkan cerita bermula tentang cara memfaktorkan semula Ansible dalam setahun dan tidak menjadi gila.

Kelahiran Legasi

Hari #1: Sifar Pesakit

Bagaimana untuk memulakan ujian Ansible, refactor projek dalam setahun dan tidak menjadi gila

Dulu ada projek bersyarat. Ia mempunyai pasukan pembangunan Dev dan jurutera Ops. Mereka menyelesaikan masalah yang sama: cara menggunakan pelayan dan menjalankan aplikasi. Masalahnya ialah setiap pasukan menyelesaikan masalah ini dengan cara tersendiri. Pada projek itu, telah diputuskan untuk menggunakan Ansible untuk menyegerakkan pengetahuan antara pasukan Dev dan Ops.

Hari #89: Kelahiran Legasi

Bagaimana untuk memulakan ujian Ansible, refactor projek dalam setahun dan tidak menjadi gila

Tanpa disedari sendiri, mereka mahu melakukannya sebaik mungkin, tetapi ia menjadi warisan. Bagaimana ini berlaku?

  • Kami mempunyai tugas yang mendesak di sini, mari kita lakukan penggodaman kotor dan kemudian perbaikinya.
  • Anda tidak perlu menulis dokumentasi dan semuanya jelas apa yang berlaku di sini.
  • Saya tahu Ansible/Python/Bash/Terraform! Lihat bagaimana saya boleh mengelak!
  • Saya seorang Pembangun Limpahan Tindanan Penuh dan menyalin ini daripada stackoverflow, saya tidak tahu bagaimana ia berfungsi, tetapi ia kelihatan hebat dan menyelesaikan masalah.

Akibatnya, anda boleh mendapatkan jenis kod yang tidak dapat difahami yang tidak ada dokumentasi, tidak jelas apa yang dilakukannya, sama ada ia diperlukan, tetapi masalahnya ialah anda perlu membangunkannya, mengubah suainya, menambah tongkat dan sokongan , memburukkan lagi keadaan.

- hosts: localhost
  tasks:
    - shell: echo -n Z >> a.txt && cat a.txt
      register: output
      delay: 1
      retries: 5
      until: not output.stdout.find("ZZZ")

Hari #109: Kesedaran tentang masalah

Bagaimana untuk memulakan ujian Ansible, refactor projek dalam setahun dan tidak menjadi gila

Model IaC yang dirangka dan dilaksanakan pada mulanya tidak lagi memenuhi keperluan pengguna / perniagaan / pasukan lain, dan masa untuk membuat perubahan pada infrastruktur tidak lagi boleh diterima. Pada masa ini, pemahaman datang bahawa sudah tiba masanya untuk mengambil tindakan.

Pemfaktoran semula IaC

Hari #139: Adakah anda benar-benar memerlukan pemfaktoran semula?

Bagaimana untuk memulakan ujian Ansible, refactor projek dalam setahun dan tidak menjadi gila

Sebelum anda tergesa-gesa untuk memfaktorkan semula, anda mesti menjawab beberapa soalan penting:

  1. Mengapa anda memerlukan semua ini?
  2. Adakah anda mempunyai masa?
  3. Cukupkah ilmu?

Jika anda tidak tahu bagaimana untuk menjawab soalan, maka pemfaktoran semula akan berakhir sebelum ia bermula, atau ia mungkin menjadi lebih teruk. Kerana mempunyai pengalaman ( Apa yang Saya Belajar daripada Menguji 200 Baris Kod Infrastruktur), maka projek itu menerima permintaan bantuan untuk menetapkan peranan dan menutupnya dengan ujian.

Hari #149: Menyediakan pemfaktoran semula

Bagaimana untuk memulakan ujian Ansible, refactor projek dalam setahun dan tidak menjadi gila

Perkara pertama ialah menyediakan. Tentukan apa yang akan kami lakukan. Untuk melakukan ini, kami berkomunikasi, mencari kawasan masalah dan mencari cara untuk menyelesaikannya. Kami merekodkan konsep yang terhasil entah bagaimana, contohnya artikel dalam pertemuan, supaya apabila timbul persoalan "apa yang terbaik?" atau "mana yang betul?" Kami tidak sesat. Dalam kes kami, kami berpegang pada idea itu membahagi dan memerintah: kami memecahkan infrastruktur kepada kepingan kecil/bata. Pendekatan ini membolehkan anda mengambil sekeping infrastruktur terpencil, memahami apa yang dilakukannya, menutupnya dengan ujian dan mengubahnya tanpa rasa takut melanggar apa-apa.

Bagaimana untuk memulakan ujian Ansible, refactor projek dalam setahun dan tidak menjadi gila

Ternyata ujian infrastruktur menjadi asas dan di sini patut disebut piramid ujian infrastruktur. Persis idea yang sama yang sedang dibangunkan, tetapi untuk infrastruktur: kami beralih daripada ujian pantas murah yang memeriksa perkara mudah, seperti lekukan, kepada ujian penuh mahal yang menggunakan keseluruhan infrastruktur.

Percubaan ujian yang boleh dipercayai

Sebelum kita pergi untuk menerangkan cara kami meliputi ujian Ansible pada projek, saya akan menerangkan percubaan dan pendekatan yang saya berpeluang gunakan sebelum ini untuk memahami konteks keputusan yang dibuat.

No. Hari -997: Peruntukan SDS

Bagaimana untuk memulakan ujian Ansible, refactor projek dalam setahun dan tidak menjadi gila

Kali pertama saya menguji Ansible adalah pada projek untuk membangunkan SDS (Software Defined Storage). Terdapat artikel berasingan mengenai topik ini
Cara memecahkan basikal di atas tongkat semasa menguji pengedaran anda, tetapi secara ringkasnya, kami berakhir dengan piramid ujian terbalik dan ujian kami menghabiskan 60-90 minit pada satu peranan, iaitu masa yang lama. Asasnya ialah ujian e2e, iaitu. kami menggunakan pemasangan sepenuhnya dan kemudian mengujinya. Apa yang lebih memburukkan ialah ciptaan basikalnya sendiri. Tetapi saya harus mengakui, penyelesaian ini berkesan dan dibenarkan untuk keluaran yang stabil.

Hari # -701: Dapur yang boleh dipercayai dan ujian

Bagaimana untuk memulakan ujian Ansible, refactor projek dalam setahun dan tidak menjadi gila

Perkembangan idea pengujian Ansible adalah penggunaan alat siap, iaitu dapur uji / dapur-ci dan inspec. Pilihannya ditentukan oleh pengetahuan tentang Ruby (untuk butiran lanjut, lihat artikel tentang HabrΓ©: Adakah pengaturcara YML bermimpi untuk menguji Ansible?) bekerja lebih cepat, kira-kira 40 minit untuk 10 peranan. Kami mencipta satu pek mesin maya dan menjalankan ujian di dalamnya.

Bagaimana untuk memulakan ujian Ansible, refactor projek dalam setahun dan tidak menjadi gila

Secara umum, penyelesaian itu berkesan, tetapi terdapat beberapa sedimen disebabkan oleh heterogen. Apabila bilangan orang yang diuji ditambah kepada 13 peranan asas dan 2 peranan meta menggabungkan peranan yang lebih kecil, maka tiba-tiba ujian mula berjalan selama 70 minit, iaitu hampir 2 kali lebih lama. Sukar untuk bercakap tentang amalan XP (pengaturcaraan melampau) kerana... tiada siapa yang mahu menunggu 70 minit. Ini adalah sebab untuk menukar pendekatan

Hari # -601: Ansible dan molekul

Bagaimana untuk memulakan ujian Ansible, refactor projek dalam setahun dan tidak menjadi gila

Dari segi konsep, ini serupa dengan dapur uji, cuma kami mengalihkan ujian peranan ke docker dan menukar timbunan. Akibatnya, masa telah dikurangkan kepada stabil 20-25 minit untuk 7 peranan.

Bagaimana untuk memulakan ujian Ansible, refactor projek dalam setahun dan tidak menjadi gila

Dengan meningkatkan bilangan peranan yang diuji kepada 17 dan memasukkan 45 peranan, kami menjalankannya dalam masa 28 minit pada 2 orang hamba jenkins.

Hari #167: Menambah ujian Ansible pada projek

Bagaimana untuk memulakan ujian Ansible, refactor projek dalam setahun dan tidak menjadi gila

Kemungkinan besar, tugas pemfaktoran semula tidak dapat dilakukan dengan tergesa-gesa. Tugas itu mesti boleh diukur supaya anda boleh memecahkannya menjadi kepingan kecil dan memakan gajah sekeping demi sekeping dengan satu sudu teh. Mesti ada pemahaman sama ada anda bergerak ke arah yang betul, berapa lama untuk pergi.

Bagaimana untuk memulakan ujian Ansible, refactor projek dalam setahun dan tidak menjadi gila

Secara umum, tidak kira bagaimana ia akan dilakukan, anda boleh menulis pada sekeping kertas, anda boleh meletakkan pelekat pada almari, anda boleh membuat tugasan dalam Jira, atau anda boleh membuka Dokumen Google dan menulis status semasa di sana. Kaki tumbuh dari fakta bahawa proses itu tidak serta-merta, ia akan menjadi panjang dan membosankan. Tidak mungkin sesiapa mahu anda kehabisan idea, letih, dan terharu semasa pemfaktoran semula.

Pemfaktoran semula adalah mudah:

  • Makan.
  • Tidur.
  • Kod.
  • ujian IaC.
  • Ulangi

dan kita ulangi ini sehingga kita mencapai matlamat yang dimaksudkan.

Bagaimana untuk memulakan ujian Ansible, refactor projek dalam setahun dan tidak menjadi gila

Ia mungkin tidak mungkin untuk mula menguji segala-galanya dengan segera, jadi tugas pertama kami adalah untuk memulakan dengan linting dan menyemak sintaks.

Hari #181: Sarjana Binaan Hijau

Bagaimana untuk memulakan ujian Ansible, refactor projek dalam setahun dan tidak menjadi gila

Linting adalah langkah pertama yang kecil ke arah Green Build Master. Ini tidak akan memecahkan hampir semua perkara, tetapi ia akan membolehkan anda menyahpepijat proses dan membuat binaan hijau dalam Jenkins. Ideanya adalah untuk membangunkan tabiat di kalangan pasukan:

  • Ujian merah adalah teruk.
  • Saya datang untuk membetulkan sesuatu dan pada masa yang sama menjadikan kod itu lebih baik sedikit daripada sebelum anda.

Hari #193: Daripada linting kepada ujian unit

Bagaimana untuk memulakan ujian Ansible, refactor projek dalam setahun dan tidak menjadi gila

Setelah membina proses memasukkan kod ke dalam induk, anda boleh memulakan proses penambahbaikan langkah demi langkah - menggantikan linting dengan pelancaran peranan, anda juga boleh melakukannya tanpa hilang upaya. Anda perlu memahami cara menggunakan peranan dan cara ia berfungsi.

Hari #211: Daripada ujian unit kepada penyepaduan

Bagaimana untuk memulakan ujian Ansible, refactor projek dalam setahun dan tidak menjadi gila

Apabila kebanyakan peranan diliputi dengan ujian unit dan semuanya digariskan, anda boleh meneruskan untuk menambah ujian penyepaduan. Itu. menguji bukan satu bata dalam infrastruktur, tetapi gabungan mereka, sebagai contoh, konfigurasi contoh penuh.

Bagaimana untuk memulakan ujian Ansible, refactor projek dalam setahun dan tidak menjadi gila

Menggunakan jenkins, kami menghasilkan banyak peringkat yang menyelaraskan peranan/buku permainan secara selari, kemudian ujian unit dalam bekas, dan akhirnya ujian penyepaduan.

Jenkins + Docker + Ansible = Ujian

Bagaimana untuk memulakan ujian Ansible, refactor projek dalam setahun dan tidak menjadi gila

  1. Semak repo dan jana peringkat binaan.
  2. Jalankan peringkat buku permainan lint secara selari.
  3. Jalankan peringkat peranan lint secara selari.
  4. Jalankan peringkat peranan semakan sintaks secara selari.
  5. Jalankan peringkat peranan ujian secara selari.
    1. Peranan linting.
    2. Semak pergantungan pada peranan lain.
    3. Semak sintaks.
    4. Buat instance docker
    5. Jalankan molekul/lalai/playbook.yml.
    6. Semak mati pucuk.
  6. Jalankan ujian integrasi
  7. Selesai

Hari #271: Faktor Bas

Bagaimana untuk memulakan ujian Ansible, refactor projek dalam setahun dan tidak menjadi gila

Pada mulanya, pemfaktoran semula dilakukan oleh sekumpulan kecil dua atau tiga orang. Mereka menyemak kod dalam induk. Dari masa ke masa, pasukan membangunkan pengetahuan tentang cara menulis kod dan semakan kod menyumbang kepada penyebaran pengetahuan tentang infrastruktur dan cara ia berfungsi. Sorotan di sini ialah penyemak telah dipilih satu demi satu, mengikut jadual, i.e. dengan beberapa tahap kebarangkalian anda akan memanjat ke dalam infrastruktur baharu.

Bagaimana untuk memulakan ujian Ansible, refactor projek dalam setahun dan tidak menjadi gila

Dan ia sepatutnya selesa di sini. Adalah mudah untuk melakukan semakan, melihat dalam rangka kerja tugas yang telah dilakukan dan sejarah perbincangan. Kami telah menyepadukan jenkins + bitbucket + jira.

Tetapi oleh itu, semakan bukanlah ubat penawar; entah bagaimana, kami masuk ke dalam kod induk, yang menjadikan kami gagal ujian:

- get_url:
    url: "{{ actk_certs }}/{{ item.1 }}"
    dest: "{{ actk_src_tmp }}/"
    username: "{{ actk_mvn_user }}"
    password: "{{ actk_mvn_pass }}"
  with_subelements:
    - "{{ actk_cert_list }}"
    - "{{ actk_certs }}"
  delegate_to: localhost

- copy:
    src: "{{ actk_src_tmp }}/{{ item.1 }}"
    dest: "{{ actk_dst_tmp }}"
  with_subelements:
    - "{{ actk_cert_list }}"
    - "{{ actk_certs }}"

Kemudian mereka membetulkannya, tetapi sisanya kekal.

get_url:
    url: "{{ actk_certs }}/{{ actk_item }}"
    dest: "{{ actk_src_tmp }}/{{ actk_item }}"
    username: "{{ actk_mvn_user }}"
    password: "{{ actk_mvn_pass }}"
  loop_control:
    loop_var: actk_item
  with_items: "{{ actk_cert_list }}"
  delegate_to: localhost

- copy:
    src: "{{ actk_src_tmp }}/{{ actk_item }}"
    dest: "{{ actk_dst_tmp }}"
  loop_control:
    loop_var: actk_item
  with_items: "{{ actk_cert_list }}"

Hari #311: Mempercepatkan ujian

Bagaimana untuk memulakan ujian Ansible, refactor projek dalam setahun dan tidak menjadi gila

Dari masa ke masa, terdapat lebih banyak ujian, binaan berjalan lebih perlahan, sehingga sejam dalam kes yang paling teruk. Pada salah satu retro terdapat frasa seperti "adalah bagus bahawa terdapat ujian, tetapi ia perlahan." Akibatnya, kami meninggalkan ujian penyepaduan pada mesin maya dan menyesuaikannya untuk Docker untuk menjadikannya lebih pantas. Kami juga menggantikan testinfra dengan pengesah yang boleh digunakan untuk mengurangkan bilangan alatan yang digunakan.

Bagaimana untuk memulakan ujian Ansible, refactor projek dalam setahun dan tidak menjadi gila

Tegasnya, terdapat satu set langkah:

  1. Beralih kepada buruh pelabuhan.
  2. Alih keluar ujian peranan, yang diduplikasi disebabkan kebergantungan.
  3. Tingkatkan bilangan hamba.
  4. Perintah ujian.
  5. Keupayaan untuk berserat SEMUA tempatan dengan satu arahan.

Bagaimana untuk memulakan ujian Ansible, refactor projek dalam setahun dan tidak menjadi gila

Akibatnya, saluran paip pada jenkins juga disatukan

  1. Menjana peringkat binaan.
  2. Lint semua selari.
  3. Jalankan peringkat peranan ujian secara selari.
  4. Selesai.

pengajaran

Elakkan pembolehubah global

Ansible menggunakan pembolehubah global, terdapat penyelesaian separa dalam bentuk private_role_vars, tetapi ini bukan ubat penawar.

Biar saya berikan satu contoh. Mari kita miliki role_a ΠΈ role_b

# cat role_a/defaults/main.yml
---
msg: a

# cat role_a/tasks/main.yml
---
- debug:
    msg: role_a={{ msg }}

# cat role_b/defaults/main.yml
---
msg: b

# cat role_b/tasks/main.yml
---
- set_fact:
    msg: b
- debug:
    msg: role_b={{ msg }}

- hosts: localhost
  vars:
    msg: hello
  roles:
    - role: role_a
    - role: role_b
  tasks:
    - debug:
        msg: play={{msg}}

Bagaimana untuk memulakan ujian Ansible, refactor projek dalam setahun dan tidak menjadi gila

Perkara yang lucu ialah hasil buku permainan akan bergantung pada perkara yang tidak selalu jelas, seperti susunan peranan disenaraikan. Malangnya, ini adalah sifat Ansible dan perkara terbaik yang boleh dilakukan ialah menggunakan beberapa jenis persetujuan, contohnya, dalam peranan, gunakan hanya pembolehubah yang diterangkan dalam peranan ini.

BAD: gunakan pembolehubah global.

# cat roles/some_role/tasks/main.yml
---
debug:
  var: java_home

BAIK: V defaults tentukan pembolehubah yang diperlukan dan kemudian gunakannya sahaja.

# cat roles/some_role/defaults/main.yml
---
r__java_home:
 "{{ java_home | default('/path') }}"

# cat roles/some_role/tasks/main.yml
---
debug:
  var: r__java_home

Pembolehubah peranan awalan

BAD: gunakan pembolehubah global.

# cat roles/some_role/defaults/main.yml
---
db_port: 5432

BAIK: Dalam peranan untuk pembolehubah, gunakan pembolehubah yang diawali dengan nama peranan; ini, dengan melihat inventori, akan memudahkan untuk memahami perkara yang berlaku.

# cat roles/some_role/defaults/main.yml
---
some_role__db_port: 5432

Gunakan pembolehubah kawalan gelung

BAD: Gunakan pembolehubah piawai dalam gelung item, jika tugasan/buku main ini disertakan di suatu tempat, ini mungkin membawa kepada tingkah laku yang tidak dijangka

---
- hosts: localhost
  tasks:
    - debug:
        msg: "{{ item }}"
      loop:
        - item1
        - item2

BAIK: Takrifkan semula pembolehubah dalam gelung melalui loop_var.

---
- hosts: localhost
  tasks:
    - debug:
        msg: "{{ item_name }}"
      loop:
        - item1
        - item2
      loop_control:
        loop_var: item_name

Semak pembolehubah input

Kami bersetuju untuk menggunakan awalan berubah-ubah; ia tidak perlu untuk memastikan bahawa ia ditakrifkan seperti yang kami jangkakan dan, sebagai contoh, tidak ditindih oleh nilai kosong.

BAIK: Semak pembolehubah.

- name: "Verify that required string variables are defined"
  assert:
    that: ahs_var is defined and ahs_var | length > 0 and ahs_var != None
    fail_msg: "{{ ahs_var }} needs to be set for the role to work "
    success_msg: "Required variables {{ ahs_var }} is defined"
  loop_control:
    loop_var: ahs_var
  with_items:
    - ahs_item1
    - ahs_item2
    - ahs_item3

Elakkan kamus cincang, gunakan struktur rata

Jika peranan menjangkakan cincang/kamus dalam salah satu parameternya, maka jika kita ingin menukar salah satu parameter anak, kita perlu mengatasi keseluruhan cincang/kamus, yang akan meningkatkan kerumitan konfigurasi.

BAD: Gunakan hash/kamus.

---
user:
  name: admin
  group: admin

BAIK: Gunakan struktur pembolehubah rata.

---
user_name: admin
user_group: "{{ user_name }}"

Cipta buku permainan & peranan idempoten

Peranan dan buku permainan mestilah idempoten, kerana mengurangkan drift konfigurasi dan ketakutan untuk memecahkan sesuatu. Tetapi jika anda menggunakan molekul, maka ini adalah tingkah laku lalai.

Elakkan menggunakan modul shell arahan

Menggunakan modul shell menghasilkan paradigma perihalan penting, bukannya deklaratif, yang merupakan teras Ansible.

Uji peranan anda melalui molekul

Molekul adalah perkara yang sangat fleksibel, mari kita lihat beberapa senario.

Molekul Berbilang contoh

Π’ molecule.yml dalam bahagian platforms anda boleh menerangkan banyak hos yang boleh anda gunakan.

---
    driver:
      name: docker
    platforms:
      - name: postgresql-instance
        hostname: postgresql-instance
        image: registry.example.com/postgres10:latest
        pre_build_image: true
        override_command: false
        network_mode: host
      - name: app-instance
        hostname: app-instance
        pre_build_image: true
        image: registry.example.com/docker_centos_ansible_tests
        network_mode: host

Oleh itu, hos ini kemudiannya boleh converge.yml gunakan:

---
- name: Converge all
  hosts: all
  vars:
    ansible_user: root
  roles:
    - role: some_role

- name: Converge db
  hosts: db-instance
  roles:
    - role: some_db_role

- name: Converge app
  hosts: app-instance
  roles:
    - role: some_app_role

Pengesah boleh dipercayai

Dalam molekul adalah mungkin untuk menggunakan ansible untuk memeriksa sama ada contoh telah dikonfigurasikan dengan betul, lebih-lebih lagi, ini telah menjadi lalai sejak keluaran 3. Ia tidak sefleksibel seperti testinfra/inspec, tetapi kami boleh menyemak sama ada kandungan fail itu sepadan dengan jangkaan kami:

---
- name: Verify
  hosts: all
  tasks:
    - name: copy config
      copy:
        src: expected_standalone.conf
        dest: /root/wildfly/bin/standalone.conf
        mode: "0644"
        owner: root
        group: root
      register: config_copy_result

    - name: Certify that standalone.conf changed
      assert:
        that: not config_copy_result.changed

Atau gunakan perkhidmatan, tunggu ia tersedia dan lakukan ujian asap:

---
  - name: Verify
    hosts: solr
    tasks:
      - command: /blah/solr/bin/solr start -s /solr_home -p 8983 -force
      - uri:
          url: http://127.0.0.1:8983/solr
          method: GET
          status_code: 200
        register: uri_result
        until: uri_result is not failed
        retries: 12
        delay: 10
      - name: Post documents to solr
        command: /blah/solr/bin/post -c master /exampledocs/books.csv

Letakkan logik kompleks ke dalam modul & pemalam

Ansible menyokong pendekatan deklaratif, jadi apabila anda melakukan percabangan kod, transformasi data, modul shell, kod menjadi sukar dibaca. Untuk memerangi perkara ini dan memastikan ia mudah difahami, tidaklah berlebihan untuk memerangi kerumitan ini dengan mencipta modul anda sendiri.

Ringkaskan Petua & Trik

  1. Elakkan pembolehubah global.
  2. Pembolehubah peranan awalan.
  3. Gunakan pembolehubah kawalan gelung.
  4. Semak pembolehubah input.
  5. Elakkan kamus cincang, gunakan struktur rata.
  6. Cipta buku permainan & peranan idempoten.
  7. Elakkan menggunakan modul shell arahan.
  8. Uji peranan anda melalui molekul.
  9. Letakkan logik kompleks ke dalam modul & pemalam.

Kesimpulan

Bagaimana untuk memulakan ujian Ansible, refactor projek dalam setahun dan tidak menjadi gila

Anda tidak boleh pergi dan memfaktorkan semula infrastruktur pada projek, walaupun anda mempunyai IaC. Ini adalah proses yang panjang yang memerlukan kesabaran, masa dan pengetahuan.

UPD1 2020.05.01 20:30 β€” Untuk profil utama buku main yang anda boleh gunakan callback_whitelist = profile_tasks untuk memahami apa yang sebenarnya berfungsi untuk masa yang lama. Kemudian kita lalui Klasik pecutan ansible. Anda juga boleh mencuba mitogen
UPD2 2020.05.03 16:34 - Versi Bahasa Inggeris

Sumber: www.habr.com

Tambah komen