Odbuduj kontener platformy Docker po zmianie pliku

86

Aby uruchomić aplikację ASP.NET Core, wygenerowałem plik dockerfile, który buduje aplikację i kopiuje kod źródłowy w kontenerze, który jest pobierany przez Git przy użyciu Jenkinsa. Dlatego w moim obszarze roboczym wykonuję następujące czynności w pliku docker:

WORKDIR /app
COPY src src

Chociaż Jenkins poprawnie aktualizuje pliki na moim hoście za pomocą Git, Docker nie stosuje tego do mojego obrazu.

Mój podstawowy skrypt do budowania:

#!/bin/bash
imageName=xx:my-image
containerName=my-container

docker build -t $imageName -f Dockerfile  .

containerRunning=$(docker inspect --format="{{ .State.Running }}" $containerName 2> /dev/null)

if [ "$containerRunning" == "true" ]; then
        docker stop $containerName
        docker start $containerName
else
        docker run -d -p 5000:5000 --name $containerName $imageName
fi

Próbowałem różnych rzeczy, takich jak --rmi --no-cacheparametrów, docker runa także zatrzymywanie / usuwanie kontenera przed zbudowaniem nowego. Nie jestem pewien, co tu robię źle. Wygląda na to, że docker poprawnie aktualizuje obraz, ponieważ wywołanie COPY src srcspowodowałoby identyfikator warstwy i brak wywołania pamięci podręcznej:

Step 6 : COPY src src
 ---> 382ef210d8fd

Jaki jest zalecany sposób aktualizacji kontenera?

Mój typowy scenariusz byłby następujący: aplikacja działa na serwerze w kontenerze Docker. Teraz części aplikacji są aktualizowane, np. Poprzez modyfikację pliku. Teraz kontener powinien działać w nowej wersji. Wydaje się, że Docker zaleca budowanie nowego obrazu zamiast modyfikowania istniejącego kontenera, więc myślę, że ogólny sposób przebudowy, tak jak ja, jest właściwy, ale niektóre szczegóły implementacji muszą zostać poprawione.

Lew
źródło
Czy możesz wymienić dokładne kroki, które wykonałeś, aby zbudować kontener, w tym polecenie kompilacji i wszystkie dane wyjściowe z każdego polecenia?
BMitch

Odpowiedzi:

136

Po kilku badaniach i testach odkryłem, że miałem pewne nieporozumienia dotyczące żywotności kontenerów Docker. Po prostu ponowne uruchomienie kontenera nie powoduje, że Docker używa nowego obrazu, gdy obraz został w międzyczasie odbudowany. Zamiast tego Docker pobiera obraz tylko przed utworzeniem kontenera. Zatem stan po uruchomieniu kontenera jest trwały.

Dlaczego usuwanie jest wymagane

Dlatego odbudowa i ponowne uruchomienie nie wystarczy. Myślałem, że kontenery działają jak usługa: zatrzymanie usługi, dokonanie zmian, ponowne uruchomienie i zastosowanie. To był mój największy błąd.

Ponieważ pojemniki są trwałe, musisz je docker rm <ContainerName>najpierw usunąć za pomocą . Po wyjęciu kontenera nie można go tak po prostu rozpocząć docker start. Należy to zrobić za pomocą docker run, który sam używa najnowszego obrazu do tworzenia nowej instancji kontenera.

Kontenery powinny być jak najbardziej niezależne

Mając tę ​​wiedzę, jest zrozumiałe, dlaczego przechowywanie danych w kontenerach jest kwalifikowane jako zła praktyka, a Docker zaleca zamiast tego woluminy danych / montowanie katalogów hosta : ponieważ kontener musi zostać zniszczony, aby zaktualizować aplikacje, przechowywane w nim dane również zostałyby utracone. Powoduje to dodatkową pracę przy zamykaniu usług, tworzeniu kopii zapasowych danych i tak dalej.

Jest to więc sprytne rozwiązanie, aby całkowicie wykluczyć te dane z kontenera: nie musimy martwić się o nasze dane, gdy są one bezpiecznie przechowywane na hoście, a kontener zawiera tylko samą aplikację.

Dlaczego -rftak naprawdę nie może ci pomóc

docker runPolecenie, ma oczyścić wyłącznik nazwie -rf. Spowoduje to zatrzymanie trwale utrzymywania kontenerów docker. Używając -rf, Docker zniszczy kontener po wyjściu z niego. Ale ten przełącznik ma dwa problemy:

  1. Docker usuwa również woluminy bez nazwy skojarzonej z kontenerem, co może spowodować utratę danych
  2. Korzystając z tej opcji, nie można uruchamiać kontenerów w tle za pomocą -dprzełącznika

Chociaż -rfprzełącznik jest dobrą opcją, aby zaoszczędzić pracę podczas programowania na potrzeby szybkich testów, jest mniej odpowiedni w środowisku produkcyjnym. Zwłaszcza z powodu braku opcji uruchomienia kontenera w tle, co byłoby najczęściej wymagane.

Jak wyjąć pojemnik

Możemy ominąć te ograniczenia, po prostu usuwając kontener:

docker rm --force <ContainerName>

Przełącznik --force(lub -f), który używa SIGKILL na uruchomionych kontenerach. Zamiast tego możesz również zatrzymać kontener przed:

docker stop <ContainerName>
docker rm <ContainerName>

Obie są równe. docker stopużywa również SIGTERM . Ale użycie --forceprzełącznika skróci skrypt, szczególnie podczas korzystania z serwerów CI: docker stopzgłasza błąd, jeśli kontener nie jest uruchomiony. To spowodowałoby, że Jenkins i wiele innych serwerów CI błędnie uznało kompilację za nieudaną. Aby to naprawić, musisz najpierw sprawdzić, czy kontener działa, tak jak ja w pytaniu (patrz containerRunningzmienna).

Pełny skrypt do przebudowy kontenera Dockera

Zgodnie z tą nową wiedzą, naprawiłem swój skrypt w następujący sposób:

#!/bin/bash
imageName=xx:my-image
containerName=my-container

docker build -t $imageName -f Dockerfile  .

echo Delete old container...
docker rm -f $containerName

echo Run new container...
docker run -d -p 5000:5000 --name $containerName $imageName

To działa idealnie :)

Lew
źródło
4
„Odkryłem, że miałem pewne nieporozumienia co do czasu życia kontenerów Dockera” - wyjąłeś te słowa z moich ust. Dziękuję za tak szczegółowe wyjaśnienie. Poleciłbym to początkującym w dockerach. To wyjaśnia różnicę między maszyną wirtualną a kontenerem.
Vince Banzon
2
Po twoim wyjaśnieniu zwróciłem uwagę na to, co zrobiłem z moim istniejącym obrazem. Aby zachować zmiany, utworzyłem nowy plik Dockerfile, aby utworzyć nowy obraz, który zawiera już zmiany, które chcę dodać. W ten sposób nowo utworzony obraz jest (w pewnym stopniu) aktualizowany.
Vince Banzon
Czy --force-recreateopcja w docker compose jest podobna do opisanej tutaj? A jeśli tak, czy nie warto byłoby zamiast tego skorzystać z tego rozwiązania (przepraszam, jeśli to głupie pytanie, ale jestem dokerem noob ^^)
cglacet
2
@cglacet Tak, jest podobny, nie można go bezpośrednio porównywać. Ale docker-composejest mądrzejszy niż zwykłe dockerpolecenia. Pracuję regularnie docker-composei wykrywanie zmian działa dobrze, więc używam --force-recreatebardzo rzadko. Tylko docker-compose up --buildjest ważne, gdy budujesz własny obraz ( builddyrektywy do redagowania pliku) zamiast obrazu z użyciem np piastę Docker.
Lion
33

Za każdym razem, gdy zostaną wprowadzone zmiany w pliku dockerfile, komponowaniu lub wymaganiach, uruchom je ponownie przy użyciu docker-compose up --build. Aby obrazy zostały odbudowane i odświeżone

yunus
źródło
1
Mając kontener Docker MySQL jako jedną usługę, czy baza danych będzie po tym pusta, jeśli użyje się woluminu /opt/mysql/data:/var/lib/mysql?
Martin Thoma
Nie wydaje mi się, aby zawsze używać go --buildw lokalnych środowiskach deweloperskich , nie ma żadnych wad . Szybkość, z jaką docker ponownie kopiuje pliki, które w innym przypadku mógłby założyć, nie wymaga kopiowania, zajmuje tylko kilka milisekund i oszczędza dużą liczbę momentów WTF.
Danack
0

Możesz uruchomić builddla określonej usługi, uruchamiając, docker-compose up --build <service name>gdzie nazwa usługi musi być zgodna z tym, jak ją nazwałeś w pliku Docker-Compose.

Przykład Załóżmy, że twój plik docker-compose zawiera wiele usług (aplikacja .net - baza danych - zaszyfrujmy ... itd.) I chcesz zaktualizować tylko aplikację .net, która została nazwana tak jak applicationw pliku docker-compose. Możesz wtedy po prostu biecdocker-compose up --build application

Dodatkowe parametry W przypadku, gdy chcesz dodać dodatkowe parametry do polecenia, takie jak -ddziałanie w tle, parametr musi znajdować się przed nazwą usługi: docker-compose up --build -d application

Aamer
źródło