标签: Docker

  • Go 服务容器化实战:用多阶段构建把镜像压到 15MB

    Go 服务容器化实战:用多阶段构建把镜像压到 15MB

    为什么镜像体积值得你花十分钟优化

    很多团队把 Go 服务容器化的第一版 Dockerfile 都是从官方文档抄来的三行脚本,能跑就行。直到某天你在 CI 里看着 1GB 的镜像一层层往上推,或者在 Kubernetes 节点上等了半分钟才拉起新 Pod,才会意识到镜像体积不是「美观问题」,而是实打实的成本与稳定性问题。

    体积大的镜像至少带来三重代价:第一,拉取慢,冷启动和滚动发布的延迟直接变长;第二,占带宽和存储,在多副本、多集群的场景下费用被放大;第三,攻击面更大,基础镜像里那些你根本用不到的 shell、包管理器、系统库,都是潜在的安全隐患。Go 是静态编译语言,理论上完全可以产出「零依赖」的极简镜像,问题在于大多数人没把这件事做对。

    单阶段构建的「原罪」

    最朴素的写法长这样:

    FROM golang:1.22
    WORKDIR /app
    COPY . .
    RUN go build -o server .
    CMD ["./server"]
    

    它的致命问题在于:golang:1.22 这个镜像自带完整的 Go 工具链、Git、shell 和一大堆系统库,体积轻松超过 900MB。而你的运行时真正需要的,只有编译出来的那个二进制文件。换句话说,你把一个「编译器」和「整套源码」一起打包进了生产镜像。

    多阶段构建:把编译和运行彻底拆开

    多阶段构建(multi-stage build)的核心思想是用一个阶段专门编译,再用另一个阶段只装运行产物。两个阶段都写在同一个 Dockerfile 里,最终镜像只保留最后一个阶段的内容。

    FROM golang:1.22 AS builder
    WORKDIR /build
    COPY go.mod go.sum ./
    RUN go mod download
    COPY . .
    RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o server .
    
    FROM gcr.io/distroless/static-debian12
    COPY --from=builder /build/server /server
    EXPOSE 8080
    ENTRYPOINT ["/server"]
    

    注意第一阶段里先单独 COPY go.mod go.sumgo mod download,是一个关键顺序优化:只要依赖不变,这一层就会被缓存,后续改业务代码时不需要重新下载依赖。

    三个编译参数决定了体积下限

    • CGO_ENABLED=0:禁用 CGO,让二进制完全静态链接,这样换到 distroless 或 scratch 这类「裸」基础镜像也不会报缺少 libc。
    • GOOS=linux:即使你在 macOS 上构建,也明确指定目标系统,避免交叉编译出来的二进制不兼容。
    • -ldflags="-s -w":去掉符号表和调试信息,通常能再砍掉 25% 左右的二进制体积。

    记住一条经验法则:镜像里每多一个二进制之外的东西,都是你为「将来可能用得上」提前付的税。生产镜像越「贫瘠」,往往越健康。

    基础镜像到底怎么选

    基础镜像典型体积包含 shell适用场景
    golang:1.22~950MB仅编译阶段
    alpine:3.20~12MB是(ash)需要轻量排障、装包的场景
    distroless/static~20MB纯静态 Go 二进制,追求最小攻击面
    scratch~0MB完全静态二进制,极致瘦身

    如果程序没有通过 net 包用到需要解析 DNS 的动态链接,scratch 是最极致的选择;但一旦你的代码里用到了 net.LookupHost 这类依赖系统解析器的逻辑,scratch 和纯 distroless/static 可能解析不了域名,这时要换成 distroless/base 或在编译时加 -tags netgo 强制用纯 Go 的 DNS 解析。

    别忘了 .dockerignore

    很多体积优化都败在 COPY . . 这一步——如果你把 node_modules.git、本地日志、测试数据一股脑拷进构建上下文,不仅变慢,还会污染镜像。新建一个 .dockerignore

    .git
    node_modules
    *.log
    tmp/
    .env
    Dockerfile
    docker-compose.yml
    

    一个可直接复用的生产级 Dockerfile

    把上面的要点收拢,下面是一个带 BuildKit 缓存挂载的完整版本,兼顾体积与构建速度:

    # syntax=docker/dockerfile:1.6
    FROM golang:1.22 AS builder
    WORKDIR /build
    COPY go.mod go.sum ./
    RUN --mount=type=cache,target=/go/pkg/mod \
        go mod download
    COPY . .
    RUN --mount=type=cache,target=/go/pkg/mod \
        --mount=type=cache,target=/root/.cache/go-build \
        CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o server .
    
    FROM gcr.io/distroless/static-debian12
    COPY --from=builder /build/server /server
    USER nonroot:nonroot
    EXPOSE 8080
    ENTRYPOINT ["/server"]
    

    进阶:用 BuildKit 缓存加速依赖层

    上面 Dockerfile 里的 --mount=type=cache 是 BuildKit 的能力。它把 Go 的模块缓存和编译缓存挂到宿主机,多次构建之间复用,能把「改一行代码重构建」的时间从几十秒压到几秒。配合 --mount=type=cache,target=/root/.cache/go-build 还能跨构建复用编译产物。

    写在最后

    把 Go 镜像从 1GB 压到 15MB,本质上没有魔法,只是把「编译环境」和「运行环境」彻底分离,并诚实地审视镜像里每一样东西是否真的必要。多阶段构建 + distroless + 正确的编译参数 + .dockerignore,这四件套足够应付绝大多数 Go 服务的生产镜像需求。下一次写 Dockerfile,先问自己一句:这一层,上线之后真的用得到吗?

  • Go部署:Docker/K8s实战配置

    Go部署:Docker/K8s实战配置

    多阶段构建:让镜像小到 10MB 以内

    Go 是静态编译语言,只需要编译后的二进制文件,不需要运行时环境。多阶段构建可以分离构建环境和运行环境,最终镜像只包含二进制文件。

    # 阶段1:构建
    FROM golang:1.23-alpine AS builder
    WORKDIR /app
    COPY go.mod go.sum ./
    RUN go mod download
    COPY . .
    RUN CGO_ENABLED=0 GOOS=linux go build -ldflags='-w -s' -o server .
    
    # 阶段2:运行
    FROM alpine:3.19
    RUN addgroup -S appgroup && adduser -S appuser -G appgroup
    WORKDIR /app
    COPY --from=builder /app/server .
    RUN chmod +x server
    USER appuser
    CMD ["./server"]

    利用 Docker BuildKit 加速构建

    # 启用 BuildKit
    export DOCKER_BUILDKIT=1
    
    # 并行下载依赖,大幅加速
    docker build --progress=plain -t myapp:latest .

    健康检查:让 K8s/Compose 知道服务已就绪

    在 Dockerfile 里加上 HEALTHCHECK,让 orchestrator 在服务真正能接收请求时才将其加入负载均衡。

    HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
      CMD wget -qO- http://localhost:8080/health || exit 1

    环境变量注入的坑

    Go 程序默认会在 ENV 改变时重新加载,但 Docker 层会缓存 ENV。用 ENTRYPOINT 脚本或 Viper 动态读取,比硬编码 ENV 常量更灵活。

    小结

    Docker 化 Go 服务的核心是:镜像要小(多阶段构建)、构建要快(BuildKit)、健康要可感知。做到这三点,生产部署就没什么大问题了。

    来源:https://www.dnote.cn

  • Docker 容器化 Go 服务的几个实战细节

    Docker 容器化 Go 服务的几个实战细节

    多阶段构建:让镜像小到 10MB 以内

    Go 是静态编译语言,只需要编译后的二进制文件,不需要运行时环境。多阶段构建可以分离构建环境和运行环境,最终镜像只包含二进制文件。

    # 阶段1:构建
    FROM golang:1.23-alpine AS builder
    WORKDIR /app
    COPY go.mod go.sum ./
    RUN go mod download
    COPY . .
    RUN CGO_ENABLED=0 GOOS=linux go build -ldflags='-w -s' -o server .
    
    # 阶段2:运行
    FROM alpine:3.19
    RUN addgroup -S appgroup && adduser -S appuser -G appgroup
    WORKDIR /app
    COPY --from=builder /app/server .
    RUN chmod +x server
    USER appuser
    CMD ["./server"]

    利用 Docker BuildKit 加速构建

    # 启用 BuildKit
    export DOCKER_BUILDKIT=1
    
    # 并行下载依赖,大幅加速
    docker build --progress=plain -t myapp:latest .

    健康检查:让 K8s/Compose 知道服务已就绪

    在 Dockerfile 里加上 HEALTHCHECK,让 orchestrator 在服务真正能接收请求时才将其加入负载均衡。

    HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
      CMD wget -qO- http://localhost:8080/health || exit 1

    环境变量注入的坑

    Go 程序默认会在 ENV 改变时重新加载,但 Docker 层会缓存 ENV。用 ENTRYPOINT 脚本或 Viper 动态读取,比硬编码 ENV 常量更灵活。

    小结

    Docker 化 Go 服务的核心是:镜像要小(多阶段构建)、构建要快(BuildKit)、健康要可感知。做到这三点,生产部署就没什么大问题了。

    来源:https://www.dnote.cn

  • 使用Docker Compose部署NextCloud和WordPress

    使用Docker Compose部署NextCloud和WordPress

    一、全局配置

    name: lnmp
    services:
      caddy:
        image: caddy:latest
        volumes:
          - ./www:/var/www/html
          - ./caddy/etc:/etc/caddy
          - ./caddy/data:/data
          - ./caddy/config:/config
        ports:
          - 80:80
          - 443:443/tcp
          - 443:443/udp
        logging:
          driver: "json-file"
          options:
            max-size: "10m"
            max-file: 3
        restart: always
    
      redis:
        image: redis:latest
        volumes:
          - ./redis/config:/etc/redis
          - ./redis/data:/data
        restart: always
        command: /etc/redis/redis.conf
    
      mysql:
        image: mysql:latest
        volumes:
          - ./mysql/config:/etc/mysql
          - ./mysql/data:/var/lib/mysql
          - ./mysql/mysql-files:/var/lib/mysql-files
        cap_add:
          - SYS_NICE
        security_opt:
          - seccomp:unconfined
        environment:
          MYSQL_ROOT_PASSWORD: password
        ports:
          - 3306:3306
        restart: always
    
      php:
        build: ./php
        volumes:
          - ./php/config:/usr/local/etc
          - ./php/logs:/var/log/php
          - ./www:/var/www/html
        depends_on:
          - caddy
          - mysql
          - redis
        cap_add:
          - SYS_PTRACE
        logging:
          driver: "json-file"
          options:
            max-size: "10m"
            max-file: 3
        restart: always
    
      imaginary:
        image: nextcloud/aio-imaginary:latest
        restart: always
        command: -concurrency 2 -enable-url-source
        environment:
          - PORT=9000
    

    1. PHP

    PHP官方的镜像启用和安装的扩展比较少,直接使用会导致WordPress和Nextcloud的健康检查一堆信息,所以使用Dockerfile来基于官方镜像构建一个专用的镜像,PHP需要的扩展包括:

    • gd(png/jpeg/gif/webp/avif)
    • imagick
    • opcache(考虑性能)
    • apcu(Nextcloud的本地缓存)
    • zip(影响WordPress插件安装)
    • redis(WordPress的对象缓存和Nextcloud的分布式缓存)
    • gmp
    • intl
    FROM registry.cn-beijing.aliyuncs.com/ianzhi/php:8.4-fpm-alpine
    
    # 配置国内镜像
    RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
    
    # 安装构建扩展相关依赖
    RUN apk add --no-cache --update --virtual .build-deps $PHPIZE_DEPS
    
    # MySQL
    RUN docker-php-ext-install pdo_mysql mysqli \
      && docker-php-ext-enable pdo_mysql mysqli
    
    # 常用扩展
    RUN docker-php-ext-install pcntl exif bcmath sysvsem \
        && docker-php-ext-enable opcache exif bcmath pcntl sysvsem
    
    # apcu
    RUN pecl install apcu && docker-php-ext-enable apcu
    
    # zip扩展
    RUN apk add --no-cache --update libzip=1.11.4-r0 libzip-dev=1.11.4-r0 unzip \
      && docker-php-ext-install zip \
      && docker-php-ext-enable zip
    
    # redis
    RUN pecl install https://pecl.php.net/get/redis-6.2.0.tgz \
      && docker-php-ext-enable redis
    
    # intl
    RUN apk add --no-cache --update icu icu-dev \
      && docker-php-ext-configure intl \
      && docker-php-ext-install intl \
      && docker-php-ext-enable intl
    
    # imagick
    RUN apk add --no-cache --update imagemagick-dev imagemagick-svg \
     && pecl install https://pecl.php.net/get/imagick-3.8.0.tgz \
     && docker-php-ext-enable imagick
    
    # gd
    RUN apk add --no-cache --update libpng libpng-dev libavif-dev libjpeg-turbo-dev freetype-dev freetype libjpeg-turbo libavif  \
      && docker-php-ext-configure gd --with-freetype --with-jpeg --with-avif \
      && docker-php-ext-install gd \
      && docker-php-ext-enable gd
    
    # ffmpeg nextcloud需要视频转码时启用
    # RUN apk add --no-cache ffmpeg
    
    # gmp nextcloud使用加密时使用
    # RUN apk add --no-cache --update gmp-dev \
    # && docker-php-ext-install gmp \
    # && docker-php-ext-enable gmp
    
    # pgsql
    # RUN apk add --no-cache --update libpq-dev postgresql-dev \
    #   && docker-php-ext-install pdo_pgsql \
    #   && docker-php-ext-enable pdo_pgsql
    
    # 删除构建依赖
    RUN apk del --no-network .build-deps
    
    # 配置文件
    RUN cp /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini