Bagaimana cara menerapkan gambar Docker yang diperbarui ke tugas Amazon ECS?

110

Apa pendekatan yang tepat untuk membuat tugas Amazon ECS saya memperbarui gambar Docker mereka, setelah gambar tersebut diperbarui di registri yang sesuai?

aknuds1
sumber
Saya akan merekomendasikan menjalankan fungsi Lambda otomatis / terjadwal. Dengan cara ini di luar contoh. Sudahkah kamu mencobanya? Anda juga dapat menggunakan SWF untuk melakukan langkah-langkah pada satu waktu
iSkore
Saya tidak perlu mengotomatiskannya @iSkore. Saya ingin menulis skrip untuk itu pada akhirnya, tetapi memilih sendiri kapan harus menjalankannya.
aknuds1
Ahh gotcha. Tidak yakin tentang itu. Bisakah Anda memberikan lebih banyak informasi?
iSkore
@iSkore Saya tidak tahu bagaimana menjelaskannya lebih baik dari yang sudah saya lakukan. Prosedurnya adalah: 1. Dorong image Docker versi baru ke registri. 2. Terapkan versi gambar baru ke ECS. Pertanyaannya adalah bagaimana menerapkan yang terakhir.
aknuds1
ini juga tidak mudah atau jelas dengan EKS..bagaimana F adalah tugas paling umum dalam menggunakan cluster, menerapkan image baru, jadi tidak jelas dalam dokumentasinya?

Jawaban:

89

Jika tugas Anda berjalan di bawah layanan, Anda dapat memaksa penerapan baru. Ini memaksa definisi tugas untuk dievaluasi ulang dan gambar container baru ditarik.

aws ecs update-service --cluster <cluster name> --service <service name> --force-new-deployment
Dima
sumber
1
Saya rasa agar ini berfungsi, Anda perlu memastikan bahwa ada cukup sumber daya pada instans ECS Anda untuk menerapkan tugas tambahan dengan ukuran yang sama. Saya berasumsi bahwa AWS pada dasarnya mencoba melakukan hotswap, menunggu contoh tugas baru untuk di-boot sebelumnya, sebelum menghentikan yang lama. Itu terus menambahkan entri "penerapan" dengan 0 instance yang sedang berjalan, jika Anda tidak melakukannya.
Alex Fedulov
3
@AlexFedulov, ya, saya pikir Anda benar. Agar tidak menimbulkan waktu henti saat membuat penerapan baru, Anda dapat 1) Menyediakan cukup instance untuk menerapkan versi baru bersama dengan versi lama. Ini dapat dicapai dengan penskalaan otomatis. 2) Gunakan tipe penyebaran Fargate. Anda dapat menghindari pengalokasian sumber daya tambahan dengan menyetel parameter "persen sehat minimum" ke 0 untuk memungkinkan ECS menghapus layanan lama Anda sebelum menerapkan yang baru. Ini akan menyebabkan beberapa waktu henti.
Dima
3
Opsi tidak diketahui: --force-new-deployment
user4674453
1
Opsi tidak diketahui: --force-new-deployment: upgrade awscli
Kyle Parisi
1
Saya mencoba perintah ini, tidak memperbarui penampung dengan gambar baru, itu memutar penampung lain dengan gambar lama yang sama. Jadi saya akhirnya memiliki dua kontainer yang berjalan meskipun dalam layanan saya memiliki hitungan yang diinginkan secara spesifik = 1
matematika
61

Setiap kali Anda memulai tugas (baik melalui panggilan StartTaskdan RunTaskAPI atau yang dimulai secara otomatis sebagai bagian dari Layanan), Agen ECS akan melakukan docker pullhal yang imageAnda tentukan dalam definisi tugas Anda. Jika Anda menggunakan nama gambar yang sama (termasuk tag) setiap kali Anda mendorong ke registri, Anda seharusnya dapat menjalankan gambar baru dengan menjalankan tugas baru. Perhatikan bahwa jika Docker tidak dapat mencapai registri karena alasan apa pun (misalnya, masalah jaringan atau masalah otentikasi), Agen ECS akan mencoba menggunakan gambar yang di-cache; jika Anda ingin menghindari gambar yang di-cache agar tidak digunakan saat Anda memperbarui gambar, Anda harus memasukkan tag yang berbeda ke registri Anda setiap kali dan memperbarui definisi tugas Anda secara bersamaan sebelum menjalankan tugas baru.

Pembaruan: Perilaku ini sekarang dapat disetel melalui ECS_IMAGE_PULL_BEHAVIORvariabel lingkungan yang disetel pada agen ECS. Lihat dokumentasi untuk detailnya. Pada saat penulisan, pengaturan berikut ini didukung:

Perilaku yang digunakan untuk menyesuaikan proses gambar tarik untuk instance container Anda. Berikut ini penjelasan tentang perilaku opsional:

  • Jika defaultditentukan, gambar ditarik dari jarak jauh. Jika penarikan gambar gagal, maka penampung menggunakan gambar yang di-cache pada instance.

  • Jika alwaysditentukan, gambar selalu ditarik dari jarak jauh. Jika penarikan gambar gagal, maka tugas gagal. Opsi ini memastikan bahwa versi gambar terbaru selalu ditarik. Semua gambar yang disimpan dalam cache akan diabaikan dan tunduk pada proses pembersihan gambar otomatis.

  • Jika onceditentukan, gambar akan ditarik dari jarak jauh hanya jika belum ditarik oleh tugas sebelumnya pada instance penampung yang sama atau jika gambar yang di-cache dihapus oleh proses pembersihan gambar otomatis. Jika tidak, gambar yang di-cache pada instance akan digunakan. Ini memastikan bahwa tidak ada penarikan gambar yang tidak perlu.

  • Jika prefer-cachedditentukan, gambar ditarik dari jarak jauh jika tidak ada gambar yang disimpan dalam cache. Jika tidak, gambar yang di-cache pada instance akan digunakan. Pembersihan gambar otomatis dinonaktifkan untuk penampung guna memastikan bahwa gambar yang disimpan dalam cache tidak dihapus.

Samuel Karp
sumber
4
Apakah kamu yakin Saya telah melihat contoh di mana gambar buruh pelabuhan lama dijalankan bahkan setelah saya mendorong gambar baru ke Dockerhub (menggunakan nama tag yang sama). Saya kira mungkin saya harus mengganti nama tag setiap kali gambar baru dibuat. Namun, ini jarang terjadi dalam pengalaman saya, jadi mungkin itu hanya masalah jaringan sesaat. (Saya sadar bahwa Anda bekerja di ECS, jadi Anda adalah orang terbaik untuk menjawab ini, tetapi ini bukan yang saya alami. Maaf jika ini dianggap tidak sopan, bukan niat saya!)
Ibrahim
1
Ya, perilaku saat ini adalah bahwa ia akan mencoba menarik setiap saat. Jika penarikan gagal (masalah jaringan, kurangnya izin, dll), itu akan mencoba menggunakan gambar yang di-cache. Anda dapat menemukan detail selengkapnya di file log agen yang biasanya ada di /var/log/ecs.
Samuel Karp
26

Mendaftarkan definisi tugas baru dan memperbarui layanan untuk menggunakan definisi tugas baru adalah pendekatan yang direkomendasikan oleh AWS. Cara termudah untuk melakukannya adalah dengan:

  1. Arahkan ke Definisi Tugas
  2. Pilih tugas yang benar
  3. Pilih buat revisi baru
  4. Jika Anda sudah menarik versi terbaru dari gambar kontainer dengan sesuatu seperti: tag terbaru, klik Buat. Jika tidak, perbarui nomor versi gambar penampung, lalu klik Buat.
  5. Luaskan Tindakan
  6. Pilih Layanan Pembaruan (dua kali)
  7. Kemudian tunggu hingga layanan dimulai ulang

Tutorial ini memiliki lebih banyak detail dan menjelaskan bagaimana langkah-langkah di atas cocok dengan proses pengembangan produk ujung ke ujung.

Pengungkapan penuh: Tutorial ini menampilkan wadah dari Bitnami dan saya bekerja untuk Bitnami. Namun pemikiran yang diungkapkan di sini adalah milik saya sendiri dan bukan pendapat Bitnami.

Neal
sumber
3
Ini berfungsi, tetapi Anda mungkin harus mengubah nilai min / maks layanan Anda. Jika Anda hanya memiliki satu instans EC2, Anda harus menyetel persen sehat minimal ke nol, jika tidak, itu tidak akan pernah menghentikan tugas (membuat layanan Anda sementara offline) untuk menerapkan penampung yang diperbarui.
Malvineous
3
@ Malvineous Poin yang bagus! Di bagian pengaturan ECS dari tutorial , saya menjelaskannya dengan tepat. Berikut konfigurasi yang direkomendasikan dari bagian itu: Jumlah tugas - 1, Persen sehat minimum - 0, Persen maksimum - 200.
Neal
@Neal Saya mencoba pendekatan Anda seperti yang dinyatakan di sini ... masih tidak ada kegembiraan
Hafiz
@Hafiz Jika Anda memerlukan bantuan untuk mengetahuinya, Anda harus menjelaskan seberapa jauh Anda mendapatkannya dan kesalahan apa yang Anda alami.
Neal
Ini hanya berfungsi untuk layanan, bukan tugas tanpa layanan.
zaitsman
9

Ada dua cara untuk melakukannya.

Pertama, gunakan AWS CodeDeploy. Anda dapat mengonfigurasi bagian penerapan Biru / Hijau dalam definisi layanan ECS. Ini termasuk CodeDeployRoleForECS, TargetGroup lain untuk switch, dan Test Listener (opsional). AWS ECS akan membuat aplikasi CodeDeploy dan grup penerapan dan menautkan sumber daya CodeDeploy ini dengan ECS Cluster / Layanan dan ELB / TargetGroup untuk Anda. Kemudian Anda dapat menggunakan CodeDeploy untuk memulai penerapan, di mana Anda perlu memasukkan AppSpec yang menentukan penggunaan tugas / penampung apa untuk memperbarui layanan apa. Di sinilah Anda menentukan tugas / wadah baru Anda. Kemudian, Anda akan melihat instance baru berputar di TargetGroup baru dan TargetGroup lama terputus ke ELB, dan segera instance lama yang terdaftar ke TargetGroup lama akan dihentikan.

Ini terdengar sangat rumit. Sebenarnya, karena / jika Anda telah mengaktifkan penskalaan otomatis pada layanan ECS Anda, cara sederhana untuk melakukannya adalah dengan memaksa penerapan baru menggunakan konsol atau cli, seperti yang ditunjukkan oleh seorang pria di sini:

aws ecs update-service --cluster <cluster name> --service <service name> --force-new-deployment

Dengan cara ini, Anda masih dapat menggunakan jenis penerapan "pembaruan berkelanjutan", dan ECS akan menjalankan instans baru dan menguras instans lama tanpa downtime layanan Anda jika semuanya baik-baik saja. Sisi buruknya adalah Anda kehilangan kendali yang baik pada penerapan dan Anda tidak dapat memutar kembali ke versi sebelumnya jika ada kesalahan dan ini akan merusak layanan yang sedang berjalan. Tapi ini adalah cara yang sangat sederhana untuk pergi.

BTW, jangan lupa untuk menetapkan angka yang tepat untuk persen sehat Minimum dan persen Maksimum, seperti 100 dan 200.

Z.Wei
sumber
Apakah ada cara untuk melakukan ini tanpa harus mengubah IP? Di milik saya ketika saya menjalankan ini, itu berhasil tetapi itu mengubah IP Pribadi yang saya jalankan
Migdotcom
@ Migdotcom Saya mengalami masalah serupa saat membutuhkan proxy NLB. Singkatnya, satu-satunya cara agar IP instans EC2 tetap sama adalah dengan menggunakan alamat IP elastis atau menggunakan pendekatan yang berbeda. Saya tidak tahu kasus penggunaan Anda tetapi menautkan Global Accelerator ke ECS yang ditautkan ALB memberi saya alamat IP statis, ini menyelesaikan kasus penggunaan saya. Jika Anda ingin mengetahui IP internal dinamis, Anda perlu menanyakan ALB dengan lambda. Ini membutuhkan banyak usaha. Tautan di bawah: aws.amazon.com/blogs/networking-and-content-delivery/…
Marcus
aws ecs update-service --cluster <cluster name> --service <service name> --force-new-deployment berhasil untuk saya!
gvasquez
3

saya menciptakan skrip untuk menerapkan image Docker yang diperbarui ke layanan staging di ECS, sehingga definisi tugas yang sesuai mengacu pada versi image Docker saat ini. Saya tidak tahu pasti apakah saya mengikuti praktik terbaik, jadi masukan akan kami terima.

Agar skrip berfungsi, Anda memerlukan instans ECS cadangan atau file deploymentConfiguration.minimumHealthyPercent nilai sehingga ECS dapat mencuri instans untuk menerapkan definisi tugas yang diperbarui.

Algoritme saya seperti ini:

  1. Beri tag pada gambar Docker yang sesuai dengan container dalam definisi tugas dengan revisi Git.
  2. Dorong tag gambar Docker ke registri yang sesuai.
  3. Batalkan pendaftaran definisi tugas lama dalam keluarga definisi tugas.
  4. Daftarkan definisi tugas baru, sekarang mengacu pada gambar Docker yang ditandai dengan revisi Git saat ini.
  5. Perbarui layanan untuk menggunakan definisi tugas baru.

Kode saya ditempel di bawah ini:

deploy-ecs

#!/usr/bin/env python3
import subprocess
import sys
import os.path
import json
import re
import argparse
import tempfile

_root_dir = os.path.abspath(os.path.normpath(os.path.dirname(__file__)))
sys.path.insert(0, _root_dir)
from _common import *


def _run_ecs_command(args):
    run_command(['aws', 'ecs', ] + args)


def _get_ecs_output(args):
    return json.loads(run_command(['aws', 'ecs', ] + args, return_stdout=True))


def _tag_image(tag, qualified_image_name, purge):
    log_info('Tagging image \'{}\' as \'{}\'...'.format(
        qualified_image_name, tag))
    log_info('Pulling image from registry in order to tag...')
    run_command(
        ['docker', 'pull', qualified_image_name], capture_stdout=False)
    run_command(['docker', 'tag', '-f', qualified_image_name, '{}:{}'.format(
        qualified_image_name, tag), ])
    log_info('Pushing image tag to registry...')
    run_command(['docker', 'push', '{}:{}'.format(
        qualified_image_name, tag), ], capture_stdout=False)
    if purge:
        log_info('Deleting pulled image...')
        run_command(
            ['docker', 'rmi', '{}:latest'.format(qualified_image_name), ])
        run_command(
            ['docker', 'rmi', '{}:{}'.format(qualified_image_name, tag), ])


def _register_task_definition(task_definition_fpath, purge):
    with open(task_definition_fpath, 'rt') as f:
        task_definition = json.loads(f.read())

    task_family = task_definition['family']

    tag = run_command([
        'git', 'rev-parse', '--short', 'HEAD', ], return_stdout=True).strip()
    for container_def in task_definition['containerDefinitions']:
        image_name = container_def['image']
        _tag_image(tag, image_name, purge)
        container_def['image'] = '{}:{}'.format(image_name, tag)

    log_info('Finding existing task definitions of family \'{}\'...'.format(
        task_family
    ))
    existing_task_definitions = _get_ecs_output(['list-task-definitions', ])[
        'taskDefinitionArns']
    for existing_task_definition in [
        td for td in existing_task_definitions if re.match(
            r'arn:aws:ecs+:[^:]+:[^:]+:task-definition/{}:\d+'.format(
                task_family),
            td)]:
        log_info('Deregistering task definition \'{}\'...'.format(
            existing_task_definition))
        _run_ecs_command([
            'deregister-task-definition', '--task-definition',
            existing_task_definition, ])

    with tempfile.NamedTemporaryFile(mode='wt', suffix='.json') as f:
        task_def_str = json.dumps(task_definition)
        f.write(task_def_str)
        f.flush()
        log_info('Registering task definition...')
        result = _get_ecs_output([
            'register-task-definition',
            '--cli-input-json', 'file://{}'.format(f.name),
        ])

    return '{}:{}'.format(task_family, result['taskDefinition']['revision'])


def _update_service(service_fpath, task_def_name):
    with open(service_fpath, 'rt') as f:
        service_config = json.loads(f.read())
    services = _get_ecs_output(['list-services', ])[
        'serviceArns']
    for service in [s for s in services if re.match(
        r'arn:aws:ecs:[^:]+:[^:]+:service/{}'.format(
            service_config['serviceName']),
        s
    )]:
        log_info('Updating service with new task definition...')
        _run_ecs_command([
            'update-service', '--service', service,
            '--task-definition', task_def_name,
        ])


parser = argparse.ArgumentParser(
    description="""Deploy latest Docker image to staging server.
The task definition file is used as the task definition, whereas
the service file is used to configure the service.
""")
parser.add_argument(
    'task_definition_file', help='Your task definition JSON file')
parser.add_argument('service_file', help='Your service JSON file')
parser.add_argument(
    '--purge_image', action='store_true', default=False,
    help='Purge Docker image after tagging?')
args = parser.parse_args()

task_definition_file = os.path.abspath(args.task_definition_file)
service_file = os.path.abspath(args.service_file)

os.chdir(_root_dir)

task_def_name = _register_task_definition(
    task_definition_file, args.purge_image)
_update_service(service_file, task_def_name)

_common.py

import sys
import subprocess


__all__ = ['log_info', 'handle_error', 'run_command', ]


def log_info(msg):
    sys.stdout.write('* {}\n'.format(msg))
    sys.stdout.flush()


def handle_error(msg):
    sys.stderr.write('* {}\n'.format(msg))
    sys.exit(1)


def run_command(
        command, ignore_error=False, return_stdout=False, capture_stdout=True):
    if not isinstance(command, (list, tuple)):
        command = [command, ]
    command_str = ' '.join(command)
    log_info('Running command {}'.format(command_str))
    try:
        if capture_stdout:
            stdout = subprocess.check_output(command)
        else:
            subprocess.check_call(command)
            stdout = None
    except subprocess.CalledProcessError as err:
        if not ignore_error:
            handle_error('Command failed: {}'.format(err))
    else:
        return stdout.decode() if return_stdout else None
aknuds1
sumber
@Andris Terima kasih, sudah diperbaiki.
aknuds1
5
Ini berlebihan. Seharusnya dapat diterapkan melalui terraform atau hanya satu baris ecs-cli.
holms
@holms Saya menggunakan Terraform untuk memperbarui gambar tugas ECS. Itu sama berlebihannya dengan kode python di atas. Langkah-langkah yang diperlukan sama rumitnya.
Jari Turkia
3

AWS CodePipeline.

Anda dapat menetapkan ECR sebagai sumber, dan ECS sebagai target untuk diterapkan.

Orang
sumber
2
dapatkah Anda menautkan ke dokumentasi apa pun untuk ini?
BenDog
1

Berikut ini berfungsi untuk saya jika tag gambar buruh pelabuhan sama:

  1. Pergi ke cluster dan layanan.
  2. Pilih layanan dan klik perbarui.
  3. Tetapkan jumlah tugas sebagai 0 dan perbarui.
  4. Setelah penerapan selesai, skala ulang jumlah tugas menjadi 1.
SaiNageswar S
sumber
1

Mengalami masalah yang sama. Setelah menghabiskan berjam-jam, telah menyimpulkan langkah-langkah yang disederhanakan ini untuk penerapan otomatis dari gambar yang diperbarui:

1. Perubahan definisi tugas ECS: Untuk pemahaman yang lebih baik, anggaplah Anda telah membuat definisi tugas dengan detail di bawah ini (catatan: angka-angka ini akan berubah sesuai definisi tugas Anda):

launch_type = EC2

desired_count = 1

Maka Anda perlu melakukan perubahan berikut:

deployment_minimum_healthy_percent = 0  //this does the trick, if not set to zero the force deployment wont happen as ECS won't allow to stop the current running task

deployment_maximum_percent = 200  //for allowing rolling update

2. Beri tag gambar Anda sebagai < your-image-name>: terbaru . Kunci terbaru menangani penarikan oleh tugas ECS masing-masing.

sudo docker build -t imageX:master .   //build your image with some tag
sudo -s eval $(aws ecr get-login --no-include-email --region us-east-1)  //login to ECR
sudo docker tag imageX:master <your_account_id>.dkr.ecr.us-east-1.amazonaws.com/<your-image-name>:latest    //tag your image with latest tag

3. Dorong gambar ke ECR

sudo docker push  <your_account_id>.dkr.ecr.us-east-1.amazonaws.com/<your-image-name>:latest

4. menerapkan penerapan paksa

sudo aws ecs update-service --cluster <your-cluster-name> --service <your-service-name> --force-new-deployment --region us-east-1

Catatan: Saya telah menulis semua perintah dengan asumsi wilayah menjadi us-east-1 . Cukup ganti dengan wilayah Anda masing-masing saat menerapkan.

Abhishek Sinha
sumber
Saya perhatikan parameternya adalah parameter terraform; Ada ide bagaimana mencapai hal yang sama untuk CloudFormation: Saya memiliki AutoScalingGroup MinSize: 0 dan MaxSize: 1; apa lagi yang perlu diatur?
Wayne
0

Menggunakan AWS cli, saya mencoba layanan pembaruan aws ecs seperti yang disarankan di atas. Tidak mengambil buruh pelabuhan terbaru dari ECR. Pada akhirnya, saya menjalankan kembali buku pedoman Yang Mungkin saya yang membuat kluster ECS. Versi definisi tugas diganti saat ecs_taskdefinition dijalankan. Maka semuanya baik-baik saja. Gambar buruh pelabuhan baru diambil.

Benar-benar tidak yakin apakah perubahan versi tugas memaksa penerapan ulang, atau jika pedoman yang menggunakan ecs_service menyebabkan tugas dimuat ulang.

Jika ada yang tertarik, saya akan mendapatkan izin untuk menerbitkan versi buku pedoman saya yang sudah dibersihkan.

mpechner.dll
sumber
Saya percaya revisi definisi tugas diperlukan hanya ketika Anda memperbarui konfigurasi definisi tugas yang sebenarnya. dalam hal ini jika Anda menggunakan gambar dengan tag terbaru, tidak perlu mengubah konfigurasi? Tentu saja memiliki commit id sebagai tag itu bagus, dan memiliki revisi definisi tugas yang terpisah juga sehingga Anda dapat melakukan rollback, tetapi CI Anda akan melihat semua kredensial yang Anda gunakan untuk container yang bukan cara saya ingin mengimplementasikan sesuatu.
holms
0

Saya juga mencoba menemukan cara otomatis untuk melakukannya, yaitu mendorong perubahan ke ECR dan kemudian tag terbaru harus diambil oleh layanan. Benar, Anda dapat melakukannya secara manual dengan Menghentikan tugas untuk layanan Anda dari cluster Anda. Tugas baru akan menarik kontainer ECR yang diperbarui.

Avijeet
sumber