Best practice deployment containere

Cei care folosiți containere, cum faceți deployment-ul, aplicația o integrați în container sau mapați în container directorul care conține aplicația?

Adică eu văd două scenarii:

  1. În Dockerfile se bagă ceva de genul, iar containerul va fi un monolit self-container și self-sufficient. Avantajul e portabilitatea fără bătai de cap. Ai nevoie să muți aplicația pe alt server, iei containerul cu totul și o pui acolo, fără alte bătăi de cap.
FROM node
WORKDIR /usr/app
COPY app/* .
RUN npm i apollo-server mariadb
CMD ["node", "index.js"]
  1. Se crează un container care conține doar engine-ul (de exemplu doar nodejs).
# Dockerfile
FROM node
WORKDIR /usr/app

Aplicația în sine este pe mașina host și este mapată în container la runtime:

# docker-compose.yml
services:
        server:
                build: .
                command: node index.js
                volumes:
                        - app:/usr/app/

Ce variantă preferați și de ce? Sau depinde de situație?

Doar varianta 1, de multi ani chiar. Fiecare imagine docker e versionata. In acest mod pot avea v1 si v2 ruland in 2 containere diferite, distribuind codul sursa la momentul build-ului, nu la runtime.

1 Like

Aplicatia in container, director afara in caz ca e baza de date (nu pentru aplicatie, ci pentru baza de date).

1 Like

Salut,

Pentru medii de prod/staging, aplicatia in container asta e clar. De evitat cam orice mapare pe host (inafara de cazuri cum ar fi o baza de date, dupa cum a fost explicat mai sus)

Pentru development (pe local) in schimb, o mapare la un director pe host a codului sursa ajuta extrem de mult ca sa nu trebuiasca sa faci build la fiecare modificare.

Regula de aur este ca un container trebuie sa fie cat se poate de sine statator ca dupa pur si simplu sa-l introduci intr-un sistem de orchestrare si sa n-ai prea mari batai de cap.

5 Likes

Mulţumesc pentru răspunsuri, are sens. Plus că mă va forța să renunț la prostul obicei de a lucra direct pe aplicațiie din producție :slight_smile:

Apropo de self-sufficient (nu găsesc o traducere potrivită), probabil știți ca podman poate rula systemd în interiorul containerului, ceea ce practic înseamnă poți folosi containerul ca pe un soi de mașină virtuală, în care să rulezi toate serviciile necesare aplicației (nginx, php, mariadb etc). Pornești containerul, pornesc toate chestiile din el. Când faci attach la container îți apare prompt-ul de logare la consolă.

Eu folosesc un astfel de container pt a ține un sistem de email complet (postfix, dovecot, mariadb, nginx/php pentru webmail și webadmin) și mi se pare foarte confortabil de lucrat în felul ăsta, că nu trebuie să-mi fac griji cu întreținerea a 1000 de containere separate. Vreau să fac upgrade la distro-ul din container? No problem, dau rebuild la container și din Fedora 34 am trecut instant la Fedora 35, cu downtime de câteva secunde, exact cât durează shutdown-ul și startup-ul.

Ce părere aveți de această abordare?

Nu e bine să pui totul laolaltă.

Fiecare serviciu cu propriul lui container și volum dacă e necesar e mai bine.
Motive: Tracing și observabilitate mai ușoară cu un agent pe podul de kubernetes în care vezi fiecare container separat și fiecare va avea logurile și metricile lui izolate. (Nu mai trebuie să adaugi un agent și în container)
Poți avea scalare orizontală.
Dacă cade un serviciu repornește tot containerul cu serviciul specific, gândește-te la un memory leak. Poți pune limite mai precise la memorie și procesor ca să economisești resurse.
Securitate mai bună, dacă apare un 0 day pe nginx nu îți afectează baza de date.

Pentru kubernetes folosești helm charts, când faci release adaugi versiunea noua la repo-ul de chart-uri intr-un anume environment și faci scale down la versiunea veche și scale up la cea nouă. Poți avea și umbrella charts care conțin mai multe chart-uri. E ușor să faci rollback la buton.
Poți folosi un tool ca harness sau spinnaker dacă ai nevoie de roluri și UI.

Nu ai nevoie de un distro în docker, folosește imaginea de alpine sau chiar distroless.

2 Likes

Mmm, uneori e problematic. De exemplu postfix nu are propriul mecanism de autentificare, iar pe vremea când foloseam Cyrus IMAP țin minte că era nevoie de un daemon separat numit saslauthd, dacă nu mă însel accesibil doar prin socket. Nu mai știu exact cum și de ce, dar pentru a se autentifica postfix-ul avea nevoie și de niște chestii din PAM.

Distro-ul unește toate chestiile astea în mod transparent și uniform, nu sunt foarte sigur că e simplu să faci toate chestiile astea în containere separate.

Probabil sunt diverse scenarii în care poți faci setup-uri bazate pe containere dedicate fiecărei componente, alteori e mai simplu să ții componentele laolaltă.

LE Apropo de chestia asta, containerele pot partaja între ele și sockets sau pot comunica doar prin IP?

Tocmai asta e problema, că unește și lucruri de care nu îți pasa. În mod normal poți izola orice serviciu din docker și expune un socket sau o rețea. Poți compune mai multe imagini intr-una.

Dacă compui mai multe imagini atunci poate merită să folosești direct kubernetes.

Nici măcar bash nu ar trebui să ai în VM dacă nu e o imagine de debug. Doar gandeste-te cu cât e mai greu cuiva să îți spargă serverul dacă n-are nimic comun pe server inafara de ce ai vrut tu.

Așa este, nu e imposibil, dar trebuie să muncească mai mult. Și evident, să nu existe vreun bug în kernel care să-i permită să evadeze din container :slight_smile:

Toate “nebuniile” astea sunt wappers peste cgroups. Unele mai simple, altele mai complexe.

Daca mai tii minte vremurile alea bune, cand ne chinuiam sa facem chroot jails pt servere, eh acum poti distribui la pachet si FSH-ul

cgroups sunt controlate de diferite sisteme de orchestrare. SystemD, LXC, OpenVZ, Docker, Kubernetes and so on

Astea fiind spuse, “design”-ul trebuie gandit intotdeauna in jurul unui singur proces intr-un jail read-only
Mai departe este treaba orchestratorului sa le grupeze in namespaces, dependinte, limite de resurse, jails

Nu le baga pe toate in unu. Foloseste HEALTHCHECK sa ii spui orchestratorului daca procesul e ok sau nu, si sa decida daca il omoara sau il restart

Daca procesul NU este unul stateless si ai nevoie sa citesti starea dupa restart-ul procesului, foloseste instructiunea VOLUME pt a defini o cale din container care sa fie persistenta. Astfel esti independent de orchestartor. E treaba lui sa faca management de volume. (*atat timp cat container-ul nu e sters/scos).
De exemplu, un update de imagine, inseamna re-creerea containerului, caz in care noul container va primi un volum persistent nou, iar toate datele tale sunt in ala vechi.

Daca nici o varianta de mai sus nu se potriveste, atunci defineste in ochestrator un volum persistent si independent de container.
Avand in vedere ca, in cazul Docker, volumul ala e dependent de host, nu e portabil cu usurinta.
Dar poti crea volume peste sisteme externe

Eg, NFS, DRBD, SSH, and so on

5 Likes

Poti face un volum legat la mai multe containere, in care sa se creeze sockets, si/sau poti face bind-mount la un socket creat in FS-ul host-ului, dar adio “security”

Pff, logic, nu m-am gândit. E suficient să partajez un director între ele, că până la urmă socket-ul e un soi de fişier. Mă rog, nu că mi-aş complic viaţa în felul ăsta, probabil diferenţa de performanţă e neglijabilă oricum.

Păi “IP” e oricum pe loop. Poate doar overhead-ul de TCP
Dar pot sa-ti zic ca am un mysql in swarm, si tine lejer la mii de conexiuni

1 Like

Apropo de jails/chroot, pentru a produce binare de Ubuntu/Debian de pe Fedora foloseam la un moment dat systemd-nspawn, ca să nu-mi bat capul cu virtualizarea.

Lăsând la o parte că docker e standardul de facto și că există puzderie unelte și de imagini gata făcute pentru el, e vreo diferență majoră între nspawn și tehnologia folosită de docker/podman?

Well … da si nu in acelasi timp

NU, pt toata lumea are nevoie de un kernel cu cgroups
DA, pt ca toata lumea si-a facut propriul API pt a rula chestiile astea peste cgroups

Also, this exists: GitHub - vbatts/nspawn-oci: OpenContainer runtime bundle wrapper around systemd-nspawn

1 Like