Membangun CI / CD Rumah dengan Tindakan GitHub dan Python

Suatu malam, ketika saya pulang kerja, saya memutuskan untuk melakukan pekerjaan rumah. Saya membuat beberapa suntingan dan segera ingin bereksperimen dengan mereka. Tetapi sebelum percobaan, saya harus masuk ke VPS, mendorong perubahan, membangun kembali wadah dan menjalankannya. Kemudian saya memutuskan bahwa sudah waktunya untuk berurusan dengan pengiriman terus menerus.


Awalnya, saya punya pilihan antara Circle CI, Travis atau Jenkins. Saya segera mengesampingkan Jenkins karena kurangnya kebutuhan akan alat yang begitu kuat. Setelah membaca singkat tentang Travis, saya sampai pada kesimpulan bahwa nyaman untuk berkumpul dan mengujinya, tetapi Anda tidak dapat membayangkan pengiriman apa pun dengannya. Saya belajar tentang Circle CI dari iklan yang terlalu mengganggu di youtube. Saya mulai bereksperimen dengan contoh-contoh, tetapi pada beberapa titik saya keliru dan saya memiliki pengujian kekal, yang membuat saya banyak menit yang berharga untuk berkumpul (Secara umum, ada batas yang cukup di sana sehingga tidak perlu khawatir, tetapi itu mengenai saya). Mengambil pencarian lagi, saya menemukan Tindakan Github. Setelah bermain dengan contoh Memulai, saya mendapat kesan positif, dan setelah melihat sekilas pada dokumentasi, saya sampai pada kesimpulan bahwa sangat keren bahwa saya dapat menyimpan rahasia untuk perakitan, mengumpulkan dan secara praktis menyebarkan proyek di satu tempat. Dengan mata berbinar, dia dengan cepat menggambar skema yang diinginkan, dan roda gigi berputar.


rencana

Pertama, kami akan mencoba melakukan pengujian. Sebagai percobaan, saya menulis server web sederhana di Flask dengan 2 titik akhir:


Mendaftar aplikasi web sederhana
from flask import Flask from flask import request, jsonify app = Flask(__name__) def validate_post_data(data: dict) -> bool: if not isinstance(data, dict): return False if not data.get('name') or not isinstance(data['name'], str): return False if data.get('age') and not isinstance(data['age'], int): return False return True @app.route('/', methods=['GET']) def hello(): return 'Hello World!' @app.route('/api', methods=['GET', 'POST']) def api(): """ /api entpoint GET - returns json= {'status': 'test'} POST - { name - str not null age - int optional } :return: """ if request.method == 'GET': return jsonify({'status': 'test'}) elif request.method == 'POST': if validate_post_data(request.json): return jsonify({'status': 'OK'}) else: return jsonify({'status': 'bad input'}), 400 def main(): app.run(host='0.0.0.0', port=8080) if __name__ == '__main__': main() 

Dan beberapa tes:


Daftar Tes Aplikasi
 import unittest import app as tested_app import json class FlaskAppTests(unittest.TestCase): def setUp(self): tested_app.app.config['TESTING'] = True self.app = tested_app.app.test_client() def test_get_hello_endpoint(self): r = self.app.get('/') self.assertEqual(r.data, b'Hello World!') def test_post_hello_endpoint(self): r = self.app.post('/') self.assertEqual(r.status_code, 405) def test_get_api_endpoint(self): r = self.app.get('/api') self.assertEqual(r.json, {'status': 'test'}) def test_correct_post_api_endpoint(self): r = self.app.post('/api', content_type='application/json', data=json.dumps({'name': 'Den', 'age': 100})) self.assertEqual(r.json, {'status': 'OK'}) self.assertEqual(r.status_code, 200) r = self.app.post('/api', content_type='application/json', data=json.dumps({'name': 'Den'})) self.assertEqual(r.json, {'status': 'OK'}) self.assertEqual(r.status_code, 200) def test_not_dict_post_api_endpoint(self): r = self.app.post('/api', content_type='application/json', data=json.dumps([{'name': 'Den'}])) self.assertEqual(r.json, {'status': 'bad input'}) self.assertEqual(r.status_code, 400) def test_no_name_post_api_endpoint(self): r = self.app.post('/api', content_type='application/json', data=json.dumps({'age': 100})) self.assertEqual(r.json, {'status': 'bad input'}) self.assertEqual(r.status_code, 400) def test_bad_age_post_api_endpoint(self): r = self.app.post('/api', content_type='application/json', data=json.dumps({'name': 'Den', 'age': '100'})) self.assertEqual(r.json, {'status': 'bad input'}) self.assertEqual(r.status_code, 400) if __name__ == '__main__': unittest.main() : 'Den', 'usia': import unittest import app as tested_app import json class FlaskAppTests(unittest.TestCase): def setUp(self): tested_app.app.config['TESTING'] = True self.app = tested_app.app.test_client() def test_get_hello_endpoint(self): r = self.app.get('/') self.assertEqual(r.data, b'Hello World!') def test_post_hello_endpoint(self): r = self.app.post('/') self.assertEqual(r.status_code, 405) def test_get_api_endpoint(self): r = self.app.get('/api') self.assertEqual(r.json, {'status': 'test'}) def test_correct_post_api_endpoint(self): r = self.app.post('/api', content_type='application/json', data=json.dumps({'name': 'Den', 'age': 100})) self.assertEqual(r.json, {'status': 'OK'}) self.assertEqual(r.status_code, 200) r = self.app.post('/api', content_type='application/json', data=json.dumps({'name': 'Den'})) self.assertEqual(r.json, {'status': 'OK'}) self.assertEqual(r.status_code, 200) def test_not_dict_post_api_endpoint(self): r = self.app.post('/api', content_type='application/json', data=json.dumps([{'name': 'Den'}])) self.assertEqual(r.json, {'status': 'bad input'}) self.assertEqual(r.status_code, 400) def test_no_name_post_api_endpoint(self): r = self.app.post('/api', content_type='application/json', data=json.dumps({'age': 100})) self.assertEqual(r.json, {'status': 'bad input'}) self.assertEqual(r.status_code, 400) def test_bad_age_post_api_endpoint(self): r = self.app.post('/api', content_type='application/json', data=json.dumps({'name': 'Den', 'age': '100'})) self.assertEqual(r.json, {'status': 'bad input'}) self.assertEqual(r.status_code, 400) if __name__ == '__main__': unittest.main() 

Output Cakupan:


 coverage report Name Stmts Miss Cover ------------------------------------------------------------------ src/app.py 28 2 93% src/tests.py 37 0 100% ------------------------------------------------------------------ TOTAL 65 2 96% 

Sekarang buat tindakan pertama kami, yang akan menjalankan tes. Menurut dokumentasi, semua tindakan harus disimpan dalam direktori khusus:


 $ mkdir -p .github/workflows $ touch .github/workflows/test_on_push.yaml 

Saya ingin tindakan ini dijalankan pada acara push apa pun di cabang mana pun, kecuali untuk rilis (tag, karena akan ada pengujian terpisah):


 on: push: tags: - '!refs/tags/*' branches: - '*' 

Kemudian kami membuat tugas yang akan berjalan di Ubuntu versi terbaru yang tersedia:


 jobs: run_tests: runs-on: [ubuntu-latest] 

Dalam langkah-langkah, kami akan memeriksa kode, menginstal python, menginstal dependensi, menjalankan tes dan cakupan tampilan:


 steps: #   - uses: actions/checkout@master #  python   - uses: actions/setup-python@v1 with: python-version: '3.8' architecture: 'x64' - name: Install requirements #   run: pip install -r requirements.txt - name: Run tests run: coverage run src/tests.py - name: Tests report run: coverage report 

Semuanya bersama
 name: Run tests on any Push event #    push    ,    . #      on: push: tags: - '!refs/tags/*' branches: - '*' jobs: run_tests: runs-on: [ubuntu-latest] steps: #   - uses: actions/checkout@master #  python   - uses: actions/setup-python@v1 with: python-version: '3.8' architecture: 'x64' - name: Install requirements #   run: pip install -r requirements.txt - name: Run tests run: coverage run src/tests.py - name: Tests report run: coverage report 

Ayo coba buat komit dan lihat bagaimana aksi kita bekerja.

Lulus tes di antarmuka Tindakan


Hore, kami berhasil membuat aksi pertama dan menjalankannya! Mari kita coba untuk memecahkan beberapa jenis tes dan lihat hasilnya:

Tes macet di antarmuka Tindakan


Tes gagal. Indikator berubah merah dan bahkan menerima notifikasi melalui pos. Apa yang kamu butuhkan! 3 dari 8 poin skema target dapat dianggap terpenuhi. Sekarang mari kita coba berurusan dengan perakitan, penyimpanan gambar buruh pelabuhan kami.


Catat! Selanjutnya kita membutuhkan akun buruh pelabuhan


Pertama, kita menulis Dockerfile sederhana di mana aplikasi kita akan dieksekusi.


Dockerfile
 #     FROM python:3.8-alpine #        /app  COPY ./ /app #    RUN apk update && pip install -r /app/requirements.txt --no-cache-dir #   (  Distutils) RUN pip install -e /app #      EXPOSE 8080 #       CMD web_server #    distutils      #CMD python /app/src/app.py 

Untuk mengirim kontainer ke hub, Anda harus masuk ke buruh pelabuhan, tetapi karena saya tidak ingin seluruh dunia mempelajari kata sandi untuk akun tersebut, saya akan menggunakan rahasia yang tertanam dalam GitHub. Secara umum, hanya kata sandi yang dapat dimasukkan ke dalam rahasia, dan sisanya akan dikodekan dalam * .yaml, dan ini akan berfungsi. Tetapi saya ingin menyalin-tempel tindakan saya tanpa perubahan, dan untuk menarik semua informasi spesifik dari rahasia.



Rahasia Github


DOCKER_LOGIN - masuk di hub.docker.com
DOCKER_PWD - kata sandi
DOCKER_NAME - nama repositori buruh pelabuhan untuk proyek ini (harus dibuat sebelumnya)


Oke, persiapan sudah selesai, sekarang mari kita buat aksi kedua kami:


 $ touch .github/workflows/pub_on_release.yaml 

Kami menyalin pengujian dari tindakan sebelumnya, dengan pengecualian pemicu peluncuran (saya tidak menemukan cara mengimpor tindakan). Kami menggantinya dengan "Luncurkan saat rilis":


 on: release: types: [published] 

PENTING! Hal ini sangat penting untuk membuat kondisi on.event yang tepat
Misalnya, jika on.release tidak menentukan jenis, maka peristiwa ini memicu setidaknya 2 peristiwa: diterbitkan dan dibuat. Bahwa, akan diluncurkan setelah 2 dari proses perakitan.


Sekarang dalam file yang sama kita akan melakukan tugas lain tergantung pada yang pertama:


 build_and_pub: needs: [run_tests] 

perlu - mengatakan bahwa tugas ini tidak akan mulai sampai run_tests berakhir


PENTING! Jika Anda memiliki beberapa file tindakan, dan di dalamnya beberapa tugas, maka semuanya dijalankan secara bersamaan di lingkungan yang berbeda. Lingkungan terpisah dibuat untuk setiap tugas, terlepas dari tugas lain. jika Anda tidak menentukan kebutuhan, maka tugas pengujian dan rakitan akan diluncurkan secara bersamaan dan terpisah satu sama lain.


Tambahkan variabel lingkungan di mana rahasia kita akan:


 env: LOGIN: ${{ secrets.DOCKER_LOGIN }} NAME: ${{ secrets.DOCKER_NAME }} 

Sekarang langkah-langkah tugas kita, di dalamnya kita harus masuk ke buruh pelabuhan, mengumpulkan wadah dan menerbitkannya di registri:


 steps: - name: Login to docker.io run: echo ${{ secrets.DOCKER_PWD }} | docker login -u ${{ secrets.DOCKER_LOGIN }} --password-stdin - uses: actions/checkout@master - name: Build image run: docker build -t $LOGIN/$NAME:${GITHUB_REF:11} -f Dockerfile . - name: Push image to docker.io run: docker push $LOGIN/$NAME:${GITHUB_REF:11} 

$ {GITHUB_REF: 11} adalah variabel github yang menyimpan baris dengan referensi ke peristiwa di mana pemicu bekerja (nama cabang, tag, dll.), Jika kita memiliki tag dengan format "v0.0.0" maka Anda perlu memangkas 11 yang pertama karakter, maka "0.0.0" tetap.


Dorong kode dan buat tag baru. Dan kami melihat bahwa kontainer kami berhasil dirakit dan dikirim ke registri, dan kami tidak menyalakan kata sandi kami di mana pun.



Bangun dan kirimkan kontainer dalam antarmuka Tindakan


Periksa hub:



Wadah disimpan di hub buruh pelabuhan


Semuanya berfungsi, tetapi tugas yang paling sulit tetap - penyebaran. Di sini Anda sudah memerlukan VPS dan alamat IP putih tempat kami akan menyebarkan wadah dan tempat Anda dapat mengirim kail. Secara teori, di sisi VPS atau server rumah, Anda dapat menjalankan skrip pada mahkota, yang dalam kasus gambar baru akan memecatnya, atau entah bagaimana bermain dengan bot telegram. Tentunya ada banyak cara untuk melakukan ini. Tapi saya akan bekerja dengan ip eksternal. Agar tidak pandai, saya menulis layanan web kecil di Flask dengan API sederhana.
Singkatnya, ada 1 titik akhir “/”.
Permintaan GET mengembalikan json dengan semua wadah aktif di host.
POST - menerima data dalam format:


 { "owner": "  ", "repository": "  ", "tag": "v0.0.1", #    "ports": {"8080": 8080, “443”: 443} #      } 

Apa yang terjadi pada tuan rumah:


  1. dari json yang diterima nama gambar baru
  2. gambar baru menggembung
  3. jika gambar telah diunduh, maka wadah saat ini dihentikan dan dihapus
  4. wadah baru diluncurkan, dengan publikasi port (-p flag)

Semua pekerjaan dengan buruh pelabuhan dilakukan menggunakan perpustakaan buruh pelabuhan


Akan sangat salah untuk menerbitkan layanan seperti itu di Internet tanpa perlindungan minimal, dan saya membuat kemiripan dengan API KEY, layanan membaca token dari variabel lingkungan, dan kemudian membandingkannya dengan header {Otorisasi: CI_TOKEN}


Daftar server web untuk ditempatkan
 # coding=utf-8 import os import sys import logging import logging.config import logging.handlers from flask import Flask from flask import request, jsonify import docker log = logging.getLogger(__name__) app = Flask(__name__) docker_client = docker.from_env() MY_AUTH_TOKEN = os.getenv('CI_TOKEN', None) #       def init_logging(): """   :return: """ log_format = f"[%(asctime)s] [ CI/CD server ] [%(levelname)s]:%(name)s:%(message)s" formatters = {'basic': {'format': log_format}} handlers = {'stdout': {'class': 'logging.StreamHandler', 'formatter': 'basic'}} level = 'INFO' handlers_names = ['stdout'] loggers = { '': { 'level': level, 'propagate': False, 'handlers': handlers_names }, } logging.basicConfig(level='INFO', format=log_format) log_config = { 'version': 1, 'disable_existing_loggers': False, 'formatters': formatters, 'handlers': handlers, 'loggers': loggers } logging.config.dictConfig(log_config) def get_active_containers(): """     :return: """ containers = docker_client.containers.list() result = [] for container in containers: result.append({ 'short_id': container.short_id, 'container_name': container.name, 'image_name': container.image.tags, 'created': container.attrs['Created'], 'status': container.status, 'ports': container.ports, }) return result def get_container_name(item: dict) -> [str, str]: """   image  POST  :param item: :return: """ if not isinstance(item, dict): return '' owner = item.get('owner') repository = item.get('repository') tag = item.get('tag', 'latest').replace('v', '') if owner and repository and tag: return f'{owner}/{repository}:{tag}', repository if repository and tag: return f'{repository}:{tag}', repository return '', '' def kill_old_container(container_name: str) -> bool: """    ,   :param container_name: :return: """ try: #    container = docker_client.containers.get(container_name) #  container.kill() except Exception as e: #       log.warning(f'Error while delete container {container_name}, {e}') return False finally: #   ,     log.debug(docker_client.containers.prune()) log.info(f'Container deleted. container_name = {container_name}') return True def deploy_new_container(image_name: str, container_name: str, ports: dict = None): try: #   image  docker hub'a log.info(f'pull {image_name}, name={container_name}') docker_client.images.pull(image_name) log.debug('Success') kill_old_container(container_name) log.debug('Old killed') #    docker_client.containers.run(image=image_name, name=container_name, detach=True, ports=ports) except Exception as e: log.error(f'Error while deploy container {container_name}, \n{e}') return {'status': False, 'error': str(e)}, 400 log.info(f'Container deployed. container_name = {container_name}') return {'status': True}, 200 @app.route('/', methods=['GET', 'POST']) def MainHandler(): """ GET -      POST -      : { "owner": "gonfff", "repository": "ci_example", "tag": "v0.0.1", "ports": {"8080": 8080} } :return: """ if request.headers.get('Authorization') != MY_AUTH_TOKEN: return jsonify({'message': 'Bad token'}), 401 if request.method == 'GET': return jsonify(get_active_containers()) elif request.method == 'POST': log.debug(f'Recieved {request.data}') image_name, container_name = get_container_name(request.json) ports = request.json.get('ports') if request.json.get('ports') else None result, status = deploy_new_container(image_name, container_name, ports) return jsonify(result), status def main(): init_logging() if not MY_AUTH_TOKEN: log.error('There is no auth token in env') sys.exit(1) app.run(host='0.0.0.0', port=5000) if __name__ == '__main__': main() format = log_format) # coding=utf-8 import os import sys import logging import logging.config import logging.handlers from flask import Flask from flask import request, jsonify import docker log = logging.getLogger(__name__) app = Flask(__name__) docker_client = docker.from_env() MY_AUTH_TOKEN = os.getenv('CI_TOKEN', None) #       def init_logging(): """   :return: """ log_format = f"[%(asctime)s] [ CI/CD server ] [%(levelname)s]:%(name)s:%(message)s" formatters = {'basic': {'format': log_format}} handlers = {'stdout': {'class': 'logging.StreamHandler', 'formatter': 'basic'}} level = 'INFO' handlers_names = ['stdout'] loggers = { '': { 'level': level, 'propagate': False, 'handlers': handlers_names }, } logging.basicConfig(level='INFO', format=log_format) log_config = { 'version': 1, 'disable_existing_loggers': False, 'formatters': formatters, 'handlers': handlers, 'loggers': loggers } logging.config.dictConfig(log_config) def get_active_containers(): """     :return: """ containers = docker_client.containers.list() result = [] for container in containers: result.append({ 'short_id': container.short_id, 'container_name': container.name, 'image_name': container.image.tags, 'created': container.attrs['Created'], 'status': container.status, 'ports': container.ports, }) return result def get_container_name(item: dict) -> [str, str]: """   image  POST  :param item: :return: """ if not isinstance(item, dict): return '' owner = item.get('owner') repository = item.get('repository') tag = item.get('tag', 'latest').replace('v', '') if owner and repository and tag: return f'{owner}/{repository}:{tag}', repository if repository and tag: return f'{repository}:{tag}', repository return '', '' def kill_old_container(container_name: str) -> bool: """    ,   :param container_name: :return: """ try: #    container = docker_client.containers.get(container_name) #  container.kill() except Exception as e: #       log.warning(f'Error while delete container {container_name}, {e}') return False finally: #   ,     log.debug(docker_client.containers.prune()) log.info(f'Container deleted. container_name = {container_name}') return True def deploy_new_container(image_name: str, container_name: str, ports: dict = None): try: #   image  docker hub'a log.info(f'pull {image_name}, name={container_name}') docker_client.images.pull(image_name) log.debug('Success') kill_old_container(container_name) log.debug('Old killed') #    docker_client.containers.run(image=image_name, name=container_name, detach=True, ports=ports) except Exception as e: log.error(f'Error while deploy container {container_name}, \n{e}') return {'status': False, 'error': str(e)}, 400 log.info(f'Container deployed. container_name = {container_name}') return {'status': True}, 200 @app.route('/', methods=['GET', 'POST']) def MainHandler(): """ GET -      POST -      : { "owner": "gonfff", "repository": "ci_example", "tag": "v0.0.1", "ports": {"8080": 8080} } :return: """ if request.headers.get('Authorization') != MY_AUTH_TOKEN: return jsonify({'message': 'Bad token'}), 401 if request.method == 'GET': return jsonify(get_active_containers()) elif request.method == 'POST': log.debug(f'Recieved {request.data}') image_name, container_name = get_container_name(request.json) ports = request.json.get('ports') if request.json.get('ports') else None result, status = deploy_new_container(image_name, container_name, ports) return jsonify(result), status def main(): init_logging() if not MY_AUTH_TOKEN: log.error('There is no auth token in env') sys.exit(1) app.run(host='0.0.0.0', port=5000) if __name__ == '__main__': main() 

Untuk aplikasi ini, saya juga melakukan setup.py untuk menginstalnya di sistem. Anda dapat menginstalnya menggunakan:


 $ python3 setup.sy install 

asalkan Anda telah mengunduh file dan berada di direktori aplikasi ini.
Setelah instalasi, Anda harus mengaktifkan aplikasi sebagai layanan sehingga ia memulai sendiri jika server reboot, untuk ini kami menggunakan systemd
Berikut ini contoh file konfigurasi:


 [Unit] Description=Deployment web server After=network-online.target [Service] Type=simple RestartSec=3 ExecStart=/usr/local/bin/ci_example Environment=CI_TOKEN=#<I generate it with $(openssl rand -hex 20)> [Install] WantedBy=multi-user.target 

Tinggal menjalankannya saja:


 $ sudo systemctl daemon-reload $ sudo systemctl enable ci_example.service $ sudo systemctl start ci_example.service 

Anda dapat melihat log server pengiriman web menggunakan perintah


 $ sudo systemctl status ci_example.service 

Bagian server sudah siap, tetap hanya untuk menambahkan hook ke tindakan kita. Untuk melakukan ini, tambahkan rahasia alamat IP dari server kami dan CI_TOKEN yang kami hasilkan ketika kami menginstal aplikasi.
Pada awalnya saya ingin menggunakan tindakan yang sudah jadi untuk curl dari pasar github, tapi sayangnya, itu menghilangkan tanda kutip dari badan permintaan POST, yang membuatnya tidak mungkin untuk mengurai json. Ini jelas tidak cocok untuk saya, dan saya memutuskan untuk menggunakan curl bawaan di ubuntu (tempat saya mengumpulkan kontainer), yang notabene memiliki efek positif pada kinerja, karena tidak memerlukan perakitan wadah tambahan:


 deploy: needs: [build_and_pub] runs-on: [ubuntu-latest] steps: - name: Set tag to env run: echo ::set-env name=TAG::$(echo ${GITHUB_REF:11}) - name: Send webhook for deploy run: "curl --silent --show-error --fail -X POST ${{ secrets.DEPLOYMENT_SERVER }} -H 'Authorization: ${{ secrets.DEPLOYMENT_TOKEN }}' -H 'Content-Type: application/json' -d '{\"owner\": \"${{ secrets.DOCKER_LOGIN }}\", \"repository\": \"${{ secrets.DOCKER_NAME }}\", \"tag\": \"${{ env.TAG }}\", \"ports\": {\"8080\": 8080}}'" 

Catatan: sangat penting untuk menentukan sakelar --fail, jika tidak, permintaan apa pun akan berhasil, bahkan jika kesalahan diterima sebagai respons.
Perlu juga dicatat bahwa variabel yang digunakan dalam permintaan tidak benar-benar variabel, tetapi fungsi khusus dipanggil dengan pengecualian GITHUB_REF, yang mengapa lama sekali saya tidak bisa mengerti mengapa permintaan itu tidak berfungsi dengan benar. Tetapi setelah membuat fungsi dari itu, semuanya berhasil.


Tindakan membangun dan menggunakan
 name: Publish on Docker Hub and Deploy on: release: types: [published] #       jobs: run_tests: #          runs-on: [ubuntu-latest] steps: #   - uses: actions/checkout@master #  python   - uses: actions/setup-python@v1 with: python-version: '3.8' architecture: 'x64' - name: Install requirements #   run: pip install -r requirements.txt - name: Run tests #   run: coverage run src/tests.py - name: Tests report run: coverage report build_and_pub: #      needs: [run_tests] runs-on: [ubuntu-latest] env: LOGIN: ${{ secrets.DOCKER_LOGIN }} NAME: ${{ secrets.DOCKER_NAME }} steps: - name: Login to docker.io #     docker.io run: echo ${{ secrets.DOCKER_PWD }} | docker login -u ${{ secrets.DOCKER_LOGIN }} --password-stdin #   - uses: actions/checkout@master - name: Build image #  image        hub.docker .. login/repository:version run: docker build -t $LOGIN/$NAME:${GITHUB_REF:11} -f Dockerfile . - name: Push image to docker.io #    registry run: docker push $LOGIN/$NAME:${GITHUB_REF:11} deploy: #         registry,      #    curl   needs: [build_and_pub] runs-on: [ubuntu-latest] steps: - name: Set tag to env run: echo ::set-env name=RELEASE_VERSION::$(echo ${GITHUB_REF:11}) - name: Send webhook for deploy run: "curl --silent --show-error --fail -X POST ${{ secrets.DEPLOYMENT_SERVER }} -H 'Authorization: ${{ secrets.DEPLOYMENT_TOKEN }}' -H 'Content-Type: application/json' -d '{\"owner\": \"${{ secrets.DOCKER_LOGIN }}\", \"repository\": \"${{ secrets.DOCKER_NAME }}\", \"tag\": \"${{ env.RELEASE_VERSION }}\", \"ports\": {\"8080\": 8080}}'" 

Oke, kita kumpulkan semuanya, sekarang kita akan membuat rilis baru dan melihat aksinya.

Webhook ke penerapan tindakan antarmuka aplikasi


Semuanya ternyata, kami membuat permintaan GET ke layanan penyebaran (menampilkan semua wadah aktif di host):

Digunakan di wadah VPS


Sekarang kami akan mengirimkan permintaan ke aplikasi yang kami gunakan:

DAPATKAN permintaan ke wadah yang digunakan


Permintaan pos ke wadah yang digunakan

Permintaan POST ke wadah yang digunakan


Kesimpulan
GitHub Actions adalah alat yang sangat nyaman dan fleksibel yang dengannya Anda dapat melakukan banyak hal yang dapat sangat menyederhanakan hidup Anda. Itu semua tergantung pada imajinasi.
Mereka mendukung pengujian integrasi dengan layanan.
Sebagai kelanjutan logis dari proyek ini dapat ditambahkan ke WebHook kemampuan untuk pengaturan transfer kustom untuk memulai wadah.
Di masa depan saya akan mencoba untuk mengambil sebagai dasar proyek ini untuk penyebaran grafik helm ketika saya belajar dan bereksperimen dengan k8s


Jika Anda memiliki semacam proyek rumahan, maka Tindakan GitHub dapat sangat mempermudah bekerja dengan repositori.


Detail sintaks dapat ditemukan di sini.
Semua sumber proyek

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


All Articles