Docker

Dockerfile

Example

FROM ubuntu

RUN apt-get update
RUN apt-get install -y python-is-python3
RUN apt-get install -y python3-pip
RUN pip3 install flask

WORKDIR /opt/source-code
COPY app.py /opt/source-code
EXPOSE 5000

ENTRYPOINT ["flask","run","--host=0.0.0.0"]

FROM

指定基礎映像檔,所有建置都必須從這裡開始。

FROM ubuntu:20.04
FROM golang:1.21-alpine

COPY

從主機複製檔案到容器中。

# 複製單一檔案
COPY app.go /app/
# 複製整個目錄
COPY src/ /app/src/

ADD

與 COPY 類似,但有額外功能:可以解壓縮檔案、從 URL 下載。

# 自動解壓縮
ADD app.tar.gz /app/
# 從 URL 下載
ADD https://example.com/file.txt /app/

RUN

執行命令並建立新的 Layer。

# 安裝套件
RUN apt-get update && apt-get install -y curl
# 執行多行命令
RUN set -ex \
    && apt-get update \
    && apt-get install -y \
    python3 \
    python3-pip

CMD

容器啟動時的預設命令。

# 執行形式
CMD ["executable", "param1", "param2"]
# Shell 形式
CMD echo "Hello World"

ENTRYPOINT

設定容器啟動時的主要命令,不容易被覆蓋。

ENTRYPOINT ["nginx", "-g", "daemon off;"]
# 與 CMD 組合使用
ENTRYPOINT ["echo"]
# 可以被 docker run 的參數覆蓋
CMD ["Hello World"]

CMD vs ENTRYPOINT

# hard code
FROM ubuntu
CMD ["sleep","5"]

# flex
FROM ubuntu
CMD ["sleep"]

# combined
FROM ubuntu
ENTRYPOINT ["sleep"]
CMD ["5"]

LABEL

為映像檔加入 metadata。

LABEL version="1.0"
LABEL description="This is my app" \
    maintainer="user@example.com"

EXPOSE

宣告容器使用的網路埠。

EXPOSE 80
EXPOSE 80/tcp
EXPOSE 80/udp

ENV

設定環境變數。

ENV APP_HOME /app
ENV VERSION=1.0 DEBUG=true

VOLUME

建立掛載點。

VOLUME /data
VOLUME ["/data", "/logs"]

USER

指定執行命令的使用者。

# 使用使用者名稱
USER nginx
# 使用 UID
USER 1000

WORKDIR

設定工作目錄。

WORKDIR /app
WORKDIR /app/src

ARG

定義建置時的變數。

ARG VERSION=latest
ARG BUILD_DATE
FROM ubuntu:${VERSION}

ONBUILD

定義子映像檔建置時的觸發指令。

ONBUILD COPY . /app/src
ONBUILD RUN /app/src/build.sh

STOPSIGNAL

設定停止容器時發送的系統信號。

STOPSIGNAL SIGTERM
STOPSIGNAL 9

SHELL

# Windows 容器範例
SHELL ["powershell", "-Command"]
# Linux 容器範例
SHELL ["/bin/bash", "-c"]

Multi-Stage Builds

多階段建置範例,用於減少最終映像檔大小。

# 建置階段
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o main .

# 最終階段
FROM alpine:latest
WORKDIR /root/
COPY --from=builder /app/main .
CMD ["./main"]

Examples

Go

# 建置階段
FROM golang:1.21-alpine AS builder

# 安裝建置工具
RUN apk add --no-cache git

# 設定工作目錄
WORKDIR /app

# 複製 go mod 檔案
COPY go.mod go.sum ./

# 下載相依套件
RUN go mod download

# 複製源碼
COPY . .

# 建置應用程式
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

# 最終階段
FROM alpine:latest

# 安裝基本工具
RUN apk --no-cache add ca-certificates tzdata

# 建立非 root 使用者
RUN adduser -D appuser

# 設定工作目錄
WORKDIR /app

# 從建置階段複製執行檔
COPY --from=builder /app/main .

# 變更擁有者
RUN chown -R appuser:appuser /app

# 切換到非 root 使用者
USER appuser

# 暴露埠口
EXPOSE 8080

# 執行應用程式
CMD ["./main"]

Java

# 建置階段
FROM maven:3.8.4-openjdk-17 AS builder

# 設定工作目錄
WORKDIR /app

# 複製 pom.xml
COPY pom.xml .

# 下載相依套件(利用 Docker 層級快取)
RUN mvn dependency:go-offline

# 複製源碼
COPY src ./src

# 建置應用程式
RUN mvn clean package -DskipTests

# 最終階段
FROM eclipse-temurin:17-jre-alpine

# 設定工作目錄
WORKDIR /app

# 建立非 root 使用者
RUN addgroup -S spring && adduser -S spring -G spring

# 設定 Java 選項
ENV JAVA_OPTS="-Xmx512m -Xms256m"

# 設定時區
ENV TZ=Asia/Taipei
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

# 複製建置產物(.jar檔案)
COPY --from=builder /app/target/*.jar app.jar

# 變更擁有者
RUN chown spring:spring /app

# 切換到非 root 使用者
USER spring

# 暴露埠口
EXPOSE 8080

# 啟動應用程式
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

Architecture

  flowchart LR
	C((Client))
	Di((Docker image))

	C --->|REST API| Di

Layer Architecture

FROM

RUN

RUN

COPY

ENTRYPOINT

Build image:

docker build Dockerfile -t lexyu/my-custom-app

Run container (COPY-ON-WRITE Mechanism):

docker run lexyu/my-custom-app
Docker Layer Architecture

Image

映像檔作為容器的基礎,用於創建和運行容器。是一種輕量級、不可變的軟體包,包含了執行應用所需的所有內容:

  • 精簡版的作業系統(Cut-down OS)
  • 必需的運行環境(執行環境)
  • 應用程式檔案(Application files)
  • 依賴的第三方庫(Third-party libraries)
  • 環境變數(Environment variables)

Engine

CLI Call Remote Container API

Containerization

Each Container:

  flowchart TD
    Ns((Namespace))
    PID(Process ID)
    UT(Unix Timesharing)
    M(Mount)
    IP(InterProcess)
    Nw(Network)

    Ns <--> PID
    Ns <--> UT
    Ns <--> M
    Ns <--> IP
    Ns <--> Nw

Namespace - PID

Namespace Overview

cgroups

cgroups

Network

Default Networks

Default Networks

User-defined Networks

User-defined Networks

Built-in DNS

Built-in DNS

Multiple Port Mapping

Multiple Port Mapping

Volume

允許資料共享:

  • 主機與容器之間
  • 容器與容器之間
Volume
docker run -v /data/mysql:/var/lib/mysql mysql
Docker Volume

使用 --mount 取代 -v

docker run --mount type=bind,source=/data/mysql,target=/var/lib/mysql mysql
Volume Mount

Add Volume into Container From Host

docker run --name website -v ($pwd):/usr/share/nginx/html:ro -d -p 9000:80 nginx
Add Volume into Container From Host

Share Volumes from Specific Container

docker run --name website-copy --volumes-from website -d -p 9001:80 nginx
Share Volume

Docker Registry

Central Repository of all Docker Images:

  • [Registry]/[User/Account]/[Image/Repository]
    • gcr.io/kubernetes-e2e-test-images/dnsutils
    • docker.io/nginx/nginx

Docker Storage

File System when install Docker in Linux:

./var/lib/docker
|_ aufs
|_ containers
|_ image
|_ volumes

Container vs Virtual Machines

Container 容器

用於執行應用程式的隔離環境,允許多個應用程式在隔離環境中運行:

  • 輕量化
  • 使用主機的作業系統
  • 啟動速度快
  • 需要較少的硬體資源
Containers

Virtual Machines 虛擬機

機器(實體硬體)的抽象化。問題:

  • 每個虛擬機需要完整的作業系統
  • 啟動速度慢
  • 資源密集
Virtual Machines

虛擬機管理程式(Hypervisor):負責管理虛擬機的程式。

Docker on Windows

Windows Container Types