Containere i Docker
Hvorfor skal jeg bruke containere istedenfor VM'er?
Containere er lettere og mer effektive enn virtuelle maskiner (VM'er) fordi de deler operativsystemets kjerne, noe som gjør dem raskere å starte opp og bruke mindre ressurser. Dette gjør at du kan kjøre flere containere på samme maskin sammenlignet med VM'er, som krever en fullstendig operativsysteminstans for hver virtuell maskin.
Kort oppsummert:
- Du sparer penger på infrastruktur
- Lettere vedlikehold på OS
- Raskere skalering av infrastruktur
- Bedre sikkerhet hvis du setter opp Kubernetes på riktig måte
Demo: Oppsett av containere med Dockerfile
I denne demoen så setter jeg opp 3 containere som har en enkel web applikasjon.
Web applikasjonen viser IP adressen, om den har kontakt med de andre containerne og hvor mye båndbredde den har mellom containerne.
Jeg kommer til å flytte disse containerne ut til AKS seinere med tanke på å demonstrere sikkerhet i AKS, CI/CD deployment av infrastruktur med Terraform og Github.
Installasjon av Docker eller Docker Desktop
For å komme i gang med containere, må du installere Docker. Du kan velge mellom Docker eller Docker Desktop, avhengig av ditt operativsystem. Docker Desktop er tilgjengelig for Windows og macOS, mens Docker kan installeres på Linux-distribusjoner.
Veiledning for Docker Desktop installasjon finner du her.
Veiledning for Docker Engine installasjon finner du her.
Dockerfile
En Dockerfile er en tekstfil som inneholder instruksjoner for å bygge en Docker-bilde.
Vi begynner med FROM
FROM sier hvilket image som skal brukes. Vi må starte med FROM for å si hvilket image som vi skal bruke
FROM ubuntu:24.04
Vi kan også spesifisere at det siste image versjon skal brukes ved å skrive:
FROM ubuntu:latest
RUN brukes for å bygge det nye laget på topp av image som vi bruker i FROM.
RUN skal oppdatere ubuntu image og installere python3, python3-pip, iperf3 og installere de Python modulene som vi trenger.
# Installer Python og pip RUN apt-get update && \ apt-get install -y python3 python3-pip python3-flask python3-requests iperf3 # Installer Flask og Requests RUN pip3 install flask requests
Jeg oppretter en mappe som heter app, hvor jeg kommer til å lagre Python koden for flask
App mappen kopierer jeg inn til image som jeg oppretter ved å bruke COPY
Jeg setter at RUN, CMD, ENTRYPOINT osv. skal kjøres fra følgende mappe med å bruke WORKDIR
COPY app /app WORKDIR /app
EXPOSE sier hvilken port som vår container skal svare på.
# Eksponer port 5000 for Flask-applikasjonen EXPOSE 5000
CMD brukes for å starte flask applikasjonen
CMD sier hvilke kommando som skal kjøres når containeren starter.
Du kan kun ha 1 CMD i din Dockerfile
CMD ["python3", "webapp.py"]
Jeg lager en enkel flask webapp, sånn at vi får bygget containeren og se at den starter etterpå.
Opprett webapp.py i mappen app og kopier inn dette innholdet:
from flask import Flask import socket app = Flask(__name__) @app.route("/") def show_ip(): ip_address = socket.gethostbyname(socket.gethostname()) return f"Container IP address: {ip_address}" if __name__ == "__main__": app.run(host="0.0.0.0", port=5000)
For å bygge Docker image, må du navigere til mappen der Dockerfile er plassert og kjøre følgende kommando:
docker build -t app1 .
docker build -t app1 . [+] Building 50.9s (9/9) FINISHED => [internal] load build definition from Dockerfile 0.1s => => transferring dockerfile: 321B 0.0s => [internal] load .dockerignore 0.1s => => transferring context: 2B 0.0s => [internal] load metadata for docker.io/library/ubuntu:latest 1.6s => [1/4] FROM docker.io/library/ubuntu:latest@sha256:b59d21599a2b151e23eea5f6602f4af4d7d31c4e236d22bf0b62b86d2e386b8f 3.2s => => resolve docker.io/library/ubuntu:latest@sha256:b59d21599a2b151e23eea5f6602f4af4d7d31c4e236d22bf0b62b86d2e386b8f 0.0s => => sha256:b59d21599a2b151e23eea5f6602f4af4d7d31c4e236d22bf0b62b86d2e386b8f 6.69kB / 6.69kB 0.0s => => sha256:04f510bf1f2528604dc2ff46b517dbdbb85c262d62eacc4aa4d3629783036096 424B / 424B 0.0s => => sha256:bf16bdcff9c96b76a6d417bd8f0a3abe0e55c0ed9bdb3549e906834e2592fd5f 2.29kB / 2.29kB 0.0s => => sha256:d9d352c11bbd3880007953ed6eec1cbace76898828f3434984a0ca60672fdf5a 29.72MB / 29.72MB 1.8s => => extracting sha256:d9d352c11bbd3880007953ed6eec1cbace76898828f3434984a0ca60672fdf5a 1.2s => [internal] load build context 0.0s => => transferring context: 667B 0.0s => [2/4] RUN apt-get update && apt-get install -y python3 python3-pip python3-flask python3-requests iperf3 43.3s => [3/4] COPY app /app 0.0s => [4/4] WORKDIR /app 0.0s => exporting to image 2.5s => => exporting layers 2.5s => => writing image sha256:245ab9dbbe335a8ef476112042a2d0ad68c52ddf4f54030acaddc88830ddc1f5 0.0s => => naming to docker.io/library/app1
Du kan se om image er opprettet ved å kjøre docker image ls
docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE app1 latest 245ab9dbbe33 2 minutes ago 592MB
Dette vil bygge et Docker image med navnet "my_flask_app" basert på Dockerfile i den gjeldende mappen.
For å kjøre Docker containeren, kan du bruke følgende kommando:
docker run -it -p 5000:5000 app1
Dette vil starte en Docker container basert på "app1" image og eksponere port 5000 på vertsmaskinen.
Du kan deretter åpne nettleseren din og navigere til http://localhost:5000 for å se Flask-applikasjonen i aksjon.
Docker containers
Du kan se hvilke containere som kjører ved å kjøre docker container ls:
Hvis du ikke ser containeren din, kan du kjøre docker container ls -all
docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES docker container ls -all CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES da88120274fb app1 "python3 webapp.py" 46 minutes ago Exited (0) 46 minutes ago compassionate_hopper
Docker nettverk
Du kan se på http://localhost:5000 at IP adressen til containeren er i rangen 172.17.0.0/16
Dette er default range for bridge network i Docker.
Bridge network er default nettverk driver i Docker
Dette er nettverks driverne som følger med som standard i Docker
Driver | Forklaring |
---|---|
bridge | default nettverk driver |
host | Remove network isolation between the container and the Docker host. |
host | Completely isolate a container from the host and other containers.. |
none | Completely isolate a container from the host and other containers. |
overlay | Overlay networks connect multiple Docker daemons together.. |
ipvlan | IPvlan networks provide full control over both IPv4 and IPv6 addressing. |
macvlan | Assign a MAC address to a container. |
Du kan se hvilke nettverk som er opprettet i Docker ved å kjøre docker network ls kommando:
docker network ls NETWORK ID NAME DRIVER SCOPE f9266d4708b3 bridge bridge local 1b2c1052f44f elastic bridge local 071da40daf5b host host local c1293ce841cb none null local
Docker App1, App2, og App3
Jeg har laget min enkle flask webapplikasjon, men ønsker å kjøre 3 av den samme applikasjonen.
De skal vise IP adressen sin på forsiden, men nå også svare på forskjellige porter.
App1 på port 5001, app2 på 5002 og app3 på port 5003.
Jeg kunne laget en Dockerfile for alle 3 applikasjonene, men da har jeg 3 applikasjoner å vedlikeholde.
Jeg gjør disse endringene til Dockerfile og webapp.py:
FROM ubuntu:latest # Installer Python og pip RUN apt-get update && \ apt-get install -y python3 python3-pip python3-flask python3-requests iperf3 COPY app /app WORKDIR /app CMD ["python3", "webapp.py"]
from flask import Flask import socket import os app = Flask(__name__) @app.route("/") def show_ip(): ip_address = socket.gethostbyname(socket.gethostname()) return f"Container IP adresse: {ip_address}" if __name__ == "__main__": port = int(os.environ.get("PORT", 5000)) app.run(host="0.0.0.0", port=port)
Siden de skal få navn app1, app2 og app3 ønsker jeg å endre navnet på imaget jeg lagde tidligere fra app1 til bare app.
docker build -t app . [+] Building 1.4s (9/9) FINISHED => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 264B 0.0s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [internal] load metadata for docker.io/library/ubuntu:latest 1.1s => [1/4] FROM docker.io/library/ubuntu:latest@sha256:b59d21599a2b151e23eea5f6602f4af4d7d31c4e236d22bf0b62b86d2e386b8f 0.0s => [internal] load build context 0.0s => => transferring context: 405B 0.0s => CACHED [2/4] RUN apt-get update && apt-get install -y python3 python3-pip python3-flask python3-requests iperf3 0.0s => [3/4] COPY app /app 0.0s => [4/4] WORKDIR /app 0.1s => exporting to image 0.1s => => exporting layers 0.1s => => writing image sha256:abcb945d0bf6d6e89b6552cd73bb7c58d8d330c54d36637b4ab172928c3c2fb5 0.0s => => naming to docker.io/library/app
Docker image er bygd på lag. Siden det ikke blir gjort endringer på RUN kan du se at den er CACHED.
Så tiden det tar å endre image blir redusert.
For å starte de 3 containerne kjører jeg kommandoene:
docker run -d -e PORT=5001 -p 5001:5001 app docker run -d -e PORT=5002 -p 5002:5002 app docker run -d -e PORT=5000 -p 5003:5000 app
-e setter ENV parametere selv om det ikke finnes i Dockerfile
PORT environment settes i OS på container og blir brukt i webapp.py
Når du spesifiserer -p så er den første delen som beskriver hvilken port Docker skal lytte til og den andre delen hvilken port containeren lytter til.
docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a1224b6286b4 app "python3 webapp.py" 5 minutes ago Up 5 minutes 0.0.0.0:5003->5000/tcp exciting_mendeleev b7a666170d35 app "python3 webapp.py" 6 minutes ago Up 6 minutes 0.0.0.0:5002->5002/tcp quirky_brown 8f0194a1f1ef app "python3 webapp.py" 6 minutes ago Up 6 minutes 0.0.0.0:5001->5001/tcp stoic_murdock
Siden jeg ikke spesifiserte navn på mine containere så får de random navn generert.
Jeg ønsker å sette mer passende navn.
Jeg kan stoppe containere ved kjøre kill kommandoen. Jeg trenger ikke skrive inn hele ID.
docker container kill 8f 8f docker container kill b7 b7 docker container kill a1 a1
Containere dukker ikke opp hvis du skriver docker container ls
For å se containere som er stoppet kan du kjøre docker container ls -all
For å slette containere som er stoppet kan du skrive docker container prune
Denne gangen starter jeg containere med å spesifisere navn:
docker run --name app1 -d -e PORT=5001 -p 5001:5001 app docker run --name app2 -d -e PORT=5002 -p 5002:5002 app docker run --name app3 -d -e PORT=5000 -p 5003:5000 app
docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 75f5ff32f3b9 app "python3 webapp.py" 3 seconds ago Up 3 seconds 0.0.0.0:5003->5000/tcp app3 4a187ff06fde app "python3 webapp.py" 8 seconds ago Up 7 seconds 0.0.0.0:5002->5002/tcp app2 159f6e026330 app "python3 webapp.py" 36 seconds ago Up 36 seconds 0.0.0.0:5001->5001/tcp app1
Jeg må nå endre web applikasjonen til å prøve å kontakte de andre på HTTP.
Hvis de får en 200 OK respons så skal de få en grønn firkant, hvis det ikke er kontakt blir den rød.
Jeg endrer webapp.py til å se slik ut:
from flask import Flask, render_template_string import socket import os import requests app = Flask(__name__) SERVERS = [ {"name": "app1", "url": "http://app1:5001/health"}, {"name": "app2", "url": "http://app2:5002/health"}, {"name": "app3", "url": "http://app3:5003/health"}, ] @app.route("/") def show_status(): ip_address = socket.gethostbyname(socket.gethostname()) results = [] for server in SERVERS: try: r = requests.get(server["url"], timeout=1) status = r.status_code == 200 except Exception: status = False results.append({"name": server["name"], "status": status}) html = """ <h2>Container IP adresse: </h2> <div style="display: flex; gap: 20px;"> {% for server in results %} <div style="width: 80px; height: 80px; background: {% if server.status %}#4CAF50{% else %}#F44336{% endif %}; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; border-radius: 10px;"> </div> {% endfor %} </div> """ return render_template_string(html, ip_address=ip_address, results=results) @app.route("/health") def health(): return "OK", 200 if __name__ == "__main__": port = int(os.environ.get("PORT", 5000)) app.run(host="0.0.0.0", port=port)
Bygg image på nytt med:
docker build -t app .
For at DNS skal fungere mellom containerne må de være på samme nettverk.
Det opprettes et nytt nettverk med kommandoen:
docker network create app-nettverk
Jeg sletter eksisterende containere og rydder opp med kommandoene:
docker container kill app1 app2 app3 docker container prune
Oppretter containere på nytt i det nye nettverket som ble opprettet:
docker run --name app1 --network app-nettverk -d -e PORT=5001 -p 5001:5001 app docker run --name app2 --network app-nettverk -d -e PORT=5002 -p 5002:5002 app docker run --name app3 --network app-nettverk -d -e PORT=5003 -p 5003:5003 app
Når du går til http://localhost:5001 skal du få opp dette:
Oppsummering
Samle kode for applikasjoner i et repo og bruke containere til test og produksjons miljø er en god fremgangsmåte.
Docker gir et team mulighet til å teste kode på samme miljø.
Når det kommer til produksjon, så trengs det en plattform som kan skalere og tilby redundans.
Det er her Kubernetes kommer inn. Jeg kommer til å bruke vår enkle webapp i neste artikkel.
Artikkelen kommer til å handle om hvordan du deployer image til Azure Kubernetes Services og hvordan du sikrer nettverket i AKS.