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:

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:

webapp

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.