GitOps 实操手册1:应用程序容器化

时间:2023-03-24 19:58:37

内容简介

本实验手册系列包含七个主题,涵盖了从应用容器化到 GitOps 工作流的实现与高级特性。通过这些实验手册,您将学习到如何构建、部署和管理应用,以及如何在 Kubernetes 集群上实现高效的持续交付流程。

以下是实验手册的七个章节:

  1. 应用程序容器化实验手册:学习如何将应用程序容器化,并为主流编程语言构建镜像。同时,了解如何缩减镜像体积和使用 GitHub Actions 自动构建镜像。
  2. 单节点 Kubernetes 集群部署实验手册:使用 kind 在本地部署单节点 Kubernetes 集群,安装基础服务,并熟悉 Kubernetes 的基本功能,如运行 Pod、调度工作负载、服务发布和自动扩缩容。
  3. 示例应用的部署和解析实验手册:深入了解示例应用的各个组件,学习如何在 Kubernetes 集群上部署、运行和管理这些组件,实现服务调用、应用配置和扩缩容等功能。
  4. 使用 Helm 定义应用实验手册:通过 Helm 对示例应用进行定义,以满足 GitOps 部署的需求。了解 Helm Chart 的创建和使用,以及预发布和生产环境的部署。
  5. 构建 GitOps 工作流实验手册:在 Kubernetes 集群上安装 ArgoCD,并使用 ArgoCD 定义示例应用进行部署,实现基于镜像版本变化触发的 GitOps 工作流。
  6. 实现 GitOps 高级发布策略实验手册:结合 ArgoCD 的高级特性,如 Argo Rollout、AnalysisTemplate 和 Argo Rollout Dashboard,实现蓝绿发布、金丝雀发布和自动渐进交付。
  7. ArgoCD 高级管理特性实验手册:探讨 ArgoCD 在 GitOps 场景中的高级特性,包括使用 ApplicationSet 实现多环境部署和使用 SealedSecret 保护密钥。

通过这七个实验手册,您将全面掌握应用容器化、Kubernetes 和 GitOps 的相关知识,为应用的持续交付提供可靠和高效的解决方案。

本章将引导您完成应用程序容器化的全过程。我们将讨论构建容器镜像的方法,探讨主流编程语言的镜像构建策略,如何有效减少镜像体积,以及如何使用GitHub Actions自动构建镜像

构建容器镜像

创建映像

创建实验目录

mkdir lab
cd lab

创建业务代码文件

nano app.py
from flask import Flask
import os
app = Flask(__name__)
app.run(debug=True)

@app.route('/')
def hello_world():
    return 'Hello, my first docker images! ' + os.getenv("HOSTNAME") + ''

这是一个简单的 Python 程序,使用 Flask Web 框架创建一个基本的 Web 服务器。下面是对代码的解释:

  1. from flask import Flask: 这一行导入了 Flask 类,这是使用 Flask 框架创建 Web 应用的基本类。
  2. import os: 这一行导入了 Python 的内置 os 模块,它提供了一个与操作系统交互的接口。
  3. app = Flask(__name__): 这一行创建了一个 Flask 应用实例。__name__ 参数通常用于确定应用的根路径。
  4. app.run(debug=True): 这一行运行了 Flask 应用,debug=True 表示应用将以调试模式运行。在调试模式下,应用会自动重新加载,如果发生错误,会显示详细的错误信息。
  5. @app.route('/'): 这是一个装饰器,它告诉 Flask 应用,当用户访问根路径(即'/')时,应该调用下面的函数。
  6. def hello_world():: 定义了一个名为 hello_world 的函数。这个函数将在用户访问根路径时被调用。
  7. return 'Hello, my first docker images! ' + os.getenv("HOSTNAME") + '': 这一行返回了一个字符串,其中包含 "Hello, my first docker images!" 以及从环境变量中获取的 "HOSTNAME" 的值。当用户访问根路径时,他们将看到这个字符串。

总之,这是一个简单的 Flask Web 应用,它在根路径上提供了一个简单的响应,包括一条欢迎消息和从环境变量中获取的 "HOSTNAME" 的值。当这个应用被放入 Docker 容器中运行时,"HOSTNAME" 将是容器的 ID。

创建 Python 的依赖文件

echo "Flask==2.2.2" >> requirements.txt

创建 Dockerfile

nano Dockerfile
# syntax=docker/dockerfile:1

FROM python:3.8-slim-buster
RUN apt-get update && apt-get install -y procps vim apache2-utils && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip3 install -r requirements.txt
COPY . .
CMD [ "python3", "-m" , "flask", "run", "--host=0.0.0.0"]

以下是对每一行的解释:

  1. # syntax=docker/dockerfile:1: 这是一个注释,指定 Dockerfile 使用的语法版本。这里的版本是 1。
  2. FROM python:3.8-slim-buster: 从名为 python:3.8-slim-buster 的基础镜像开始构建新的 Docker 镜像。这个镜像基于 Debian Buster,并包含了预安装的 Python 3.8 和一些最小化工具。
  3. RUN apt-get update && apt-get install -y procps vim apache2-utils && rm -rf /var/lib/apt/lists/*: 这个 RUN 指令执行了一系列命令,用于更新包列表、安装一些额外的软件包(如 procps、vim 和 apache2-utils)以及清理缓存文件。这些软件包在新的镜像中将可用。
  4. WORKDIR /app: 设置工作目录为 /app。接下来的指令(如 COPY 和 RUN)将在这个目录下执行。
  5. COPY requirements.txt requirements.txt: 将当前构建上下文中的 requirements.txt 文件复制到镜像的工作目录中。这个文件包含了应用所需的 Python 包列表。
  6. RUN pip3 install -r requirements.txt: 安装 requirements.txt 文件中列出的 Python 包。这些包将包含在新的镜像中,供应用程序使用。
  7. COPY . .: 将当前构建上下文中的所有文件和目录复制到镜像的工作目录中。这通常包括应用程序的源代码。
  8. CMD [ "python3", "-m" , "flask", "run", "--host=0.0.0.0"]: 设置容器的默认命令。当容器启动时,这个命令将被执行。这里的命令启动了 Flask 应用,并使用 --host=0.0.0.0 参数允许外部访问。

构建映像

root@node1:~/lab# dir
app.py  Dockerfile  requirements.txt
docker build -t hello-world-flask .

查看映像

docker iamges
ubuntu $ docker images
REPOSITORY          TAG               IMAGE ID       CREATED              SIZE
hello-world-flask   latest            56f87d5a4218   About a minute ago   163MB
python              3.8-slim-buster   a87430669f7e   38 hours ago         116MB

运行容器,并查看容器页面

docker run -d -p 8000:5000 hello-world-flask:latest

docker ps 

curl localhost:8000
$ docker run -d -p 8000:5000 hello-world-flask:latest
50f36bc7e03e925030d81677a0848be2b02d97eb9a71129de3813e03fa4879a7
ubuntu $ docker ps    
CONTAINER ID   IMAGE                      COMMAND                  CREATED         STATUS         PORTS                                       NAMES
50f36bc7e03e   hello-world-flask:latest   "python3 -m flask ru…"   8 seconds ago   Up 7 seconds   0.0.0.0:8000->5000/tcp, :::8000->5000/tcp   friendly_carver
ubuntu $ curl localhost:8000
Hello, my first docker images! 50f36bc7e03eubuntu $

这里总结了将业务代码构建为容器镜像的基本步骤:

  1. 使用 FROM 命令指定一个已经安装了特定编程语言编译工具的基础镜像。在官方镜像仓库中可以找到所需的任何基础镜像。例如,对于 Java,可以使用 eclipse-temurin:17-jdk-jammy;对于 Golang,可以使用 golang:1.16-alpine
  2. 使用 WORKDIR 命令配置镜像的工作目录,如 WORKDIR /app
  3. 使用 COPY 命令将本地目录的源码复制到镜像的工作目录下,例如 COPY .
  4. 使用 RUN 命令下载业务依赖,例如 pip3 install。如果是静态语言,则需要进一步编译源码生成可执行文件。
  5. 最后,使用 CMD 命令配置镜像的启动命令,即启动业务代码。

上传映像到 hub.docker.com

登录到容器映像库

docker login

给本地映像打标签

docker tag hello-world-flask chengzh/hello-world-flask

推送映像

docker push chengzh/hello-world-flask
ubuntu $ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: chengzh
Password: 
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded
ubuntu $ docker tag hello-world-flask chengzh/hello-world-flask
ubuntu $ docker images
REPOSITORY                  TAG               IMAGE ID       CREATED         SIZE
chengzh/hello-world-flask   latest            56f87d5a4218   7 minutes ago   163MB
hello-world-flask           latest            56f87d5a4218   7 minutes ago   163MB
python                      3.8-slim-buster   a87430669f7e   38 hours ago    116MB
ubuntu $ docker push chengzh/hello-world-flask
Using default tag: latest
The push refers to repository [docker.io/chengzh/hello-world-flask]
101fce4be7c9: Pushed 
e3cd84948409: Pushed 
728fc464d546: Pushed 
042c8a4e5706: Pushed 
1176f885248c: Pushed 
3dc8c69b841e: Mounted from library/python 
e85196541518: Mounted from library/python 
326bef06dac1: Mounted from library/python 
748ccc4fc823: Mounted from library/python 
60333954a7a8: Mounted from library/python 
latest: digest: sha256:6cd2283913db39d4d45620d073443c4c3a2cc91b25118f4236f205556bc84044 size: 2414

在另外一台 docker 宿主机上测试

下载映像

docker pull chengzh/hello-world-flask:latest

查看映像

docker images
ubuntu $ docker images
REPOSITORY                  TAG       IMAGE ID       CREATED          SIZE
chengzh/hello-world-flask   latest    56f87d5a4218   13 minutes ago   163MB

启动容器

docker run -d -p 8000:5000 chengzh/hello-world-flask:latest

查看容器

docker ps
ubuntu $ docker ps
CONTAINER ID   IMAGE                              COMMAND                  CREATED         STATUS         PORTS                                       NAMES
12dbcd7f0621   chengzh/hello-world-flask:latest   "python3 -m flask ru…"   2 minutes ago   Up 2 minutes   0.0.0.0:8000->5000/tcp, :::8000->5000/tcp   bold_lumiere

访问宿主机8000端口

curl localhost:8000
Hello, my first docker images! 12dbcd7f0621

和容器进行交互

docker exec -it 12dbcd7f0621 bash
ubuntu $  docker exec -it 12dbcd7f0621 bash
root@12dbcd7f0621:/app# ls
Dockerfile  __pycache__  app.py  requirements.txt
root@12dbcd7f0621:/app#

为不同语言构建镜像

Java 应用容器化 启动 Jar 包的构建方式

git clone https://github.com/cloudzun/gitops
cd gitops/docker/13/spring-boot
ls -al
root@node1:~# cd gitops/docker/13/spring-boot
root@node1:~/gitops/docker/13/spring-boot# ls -al
total 56
drwxr-xr-x 4 root root  4096 Feb 11 12:42 .
drwxr-xr-x 7 root root  4096 Feb 11 12:42 ..
-rw-r--r-- 1 root root   367 Feb 11 12:42 Dockerfile
-rw-r--r-- 1 root root   196 Feb 11 12:42 Dockerfile-Boot
-rw-r--r-- 1 root root     6 Feb 11 12:42 .dockerignore
-rw-r--r-- 1 root root   395 Feb 11 12:42 .gitignore
drwxr-xr-x 3 root root  4096 Feb 11 12:42 .mvn
-rwxr-xr-x 1 root root 10284 Feb 11 12:42 mvnw
-rw-r--r-- 1 root root  6734 Feb 11 12:42 mvnw.cmd
-rw-r--r-- 1 root root  1226 Feb 11 12:42 pom.xml
drwxr-xr-x 4 root root  4096 Feb 11 12:42 src
nano src/main/java/com/example/demo/DemoApplication.java
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class DemoApplication {
        public static void main(String[] args) {
                SpringApplication.run(DemoApplication.class, args);
        }

        @GetMapping("/hello")
        public String hello(@RequestParam(value = "name", defaultValue = "World") String name) {
                return String.format("Hello %s!", name);
        }
}

内容是 Demo 应用的主体文件,它包含一个 /hello 接口,使用 Get 请求访问后会返回 “Hello World”。

nano Dockerfile
# syntax=docker/dockerfile:1

FROM eclipse-temurin:17-jdk-jammy as builder
WORKDIR /opt/app
COPY .mvn/ .mvn
COPY mvnw pom.xml ./
RUN ./mvnw dependency:go-offline
COPY ./src ./src
RUN ./mvnw clean install


FROM eclipse-temurin:17-jre-jammy
WORKDIR /opt/app
EXPOSE 8080
COPY --from=builder /opt/app/target/*.jar /opt/app/*.jar
CMD ["java", "-jar", "/opt/app/*.jar" ]

这是一个 Dockerfile,用于构建一个基于 Maven 构建的 Java Web 应用的容器镜像。以下是对每条指令的解释:

  1. FROM eclipse-temurin:17-jdk-jammy as builder:从官方的 Eclipse Temurin 镜像中拉取一个标记为 17-jdk-jammy 的版本,并将其命名为 builder。
  2. WORKDIR /opt/app:在容器内设置工作目录为 /opt/app。
  3. COPY .mvn/ .mvn:将本地目录下的 .mvn 文件夹复制到容器内的 .mvn 文件夹。
  4. COPY mvnw pom.xml ./:将本地目录下的 mvnw 和 pom.xml 文件复制到容器内的当前工作目录。
  5. RUN ./mvnw dependency:go-offline:在容器内运行 ./mvnw dependency:go-offline 命令,下载应用所需的依赖包并缓存到本地。
  6. COPY ./src ./src:将本地目录下的 ./src 文件夹复制到容器内的 ./src 文件夹。
  7. RUN ./mvnw clean install:在容器内运行 ./mvnw clean install 命令,使用 Maven 构建应用并打包成 jar。
  8. FROM eclipse-temurin:17-jre-jammy:从官方的 Eclipse Temurin 镜像中拉取一个标记为 17-jre-jammy 的版本。
  9. WORKDIR /opt/app:在容器内设置工作目录为 /opt/app。
  10. EXPOSE 8080:声明镜像运行时监听的端口号为 8080。
  11. COPY --from=builder /opt/app/target/*.jar /opt/app/*.jar:从之前命名为 builder 的容器中,将构建好的 jar 文件复制到当前容器的 /opt/app 目录下。
  12. CMD ["java", "-jar", "/opt/app/*.jar" ]:设置容器启动时执行的默认命令,即运行 /opt/app 目录下的所有 jar 文件。
docker build -t spring-boot .
docker run --publish 8080:8080 spring-boot
curl localhost:8080/hello
root@node1:~# curl localhost:8080/hello
Hello World!root@node1:~#

Spring Boot 插件的构建方式

nano Dockerfile-Boot
# syntax=docker/dockerfile:1

FROM eclipse-temurin:17-jdk-jammy

WORKDIR /app

COPY .mvn/ .mvn
COPY mvnw pom.xml ./
RUN ./mvnw dependency:resolve

COPY src ./src

CMD ["./mvnw", "spring-boot:run"]

这是一个 Dockerfile,用于构建一个基于 Maven 构建的 Java Web 应用的容器镜像。以下是对每条指令的解释:

  1. FROM eclipse-temurin:17-jdk-jammy:从官方的 Eclipse Temurin 镜像中拉取一个标记为 17-jdk-jammy 的版本。
  2. WORKDIR /app:在容器内设置工作目录为 /app。
  3. COPY .mvn/ .mvn:将本地目录下的 .mvn 文件夹复制到容器内的 .mvn 文件夹。
  4. COPY mvnw pom.xml ./:将本地目录下的 mvnw 和 pom.xml 文件复制到容器内的当前工作目录。
  5. RUN ./mvnw dependency:resolve:在容器内运行 ./mvnw dependency:resolve 命令,下载应用所需的依赖包并缓存到本地。
  6. COPY src ./src:将本地目录下的 ./src 文件夹复制到容器内的 ./src 文件夹。
  7. CMD ["./mvnw", "spring-boot:run"]:设置容器启动时执行的默认命令,即使用 Maven 启动 Spring Boot 应用。
docker build -t spring-boot . -f Dockerfile-Boot
docker run --publish 8080:8080 spring-boot
root@node1:~# curl localhost:8080/hello
Hello World!root@node1:~#
docker images
root@node1:~/gitops/docker/13/spring-boot# docker images
REPOSITORY                       TAG            IMAGE ID       CREATED          SIZE
spring-boot                      latest         a61a851ebb1e   17 seconds ago   525MB
<none>                           <none>         bf0a8dd08bed   24 minutes ago   284MB

Golang 应用容器化

root@node1:~/gitops/docker/13/golang# dir
Dockerfile    Dockerfile-2  Dockerfile-4  Dockerfile-6  example  go.sum
Dockerfile-1  Dockerfile-3  Dockerfile-5  Dockerfile-7  go.mod   main.go
nano main.go
package main

import (
        "net/http"
        "github.com/labstack/echo/v4"
)

func main() {
        e := echo.New()
        e.GET("/hello", func(c echo.Context) error {
                return c.String(http.StatusOK, "Hello World Golang")
        })
        e.Logger.Fatal(e.Start(":8080"))
}

这是一个简单的 Golang 程序,它使用 Echo 框架创建了一个 HTTP 服务器。服务器会监听 8080 端口,并为 "/hello" 路径提供一个响应。

nano Dockerfile
# syntax=docker/dockerfile:1
FROM golang:1.17 as builder

WORKDIR /opt/app
COPY go.* ./
RUN go mod download

COPY . .

ARG LD_FLAGS='-linkmode external -w -extldflags "-static"'
RUN go build -ldflags "$LD_FLAGS" -o example

FROM alpine as runner
WORKDIR /opt/app
COPY --from=builder /opt/app/example /opt/app/example

EXPOSE 8080
CMD ["/opt/app/example"]

这是一个 Dockerfile,用于构建一个 Go Web 应用的容器镜像。以下是对每条指令的解释:

  1. FROM golang:1.17 as builder:从官方的 Golang 镜像中拉取一个标记为 1.17 的版本,并将其命名为 builder。
  2. WORKDIR /opt/app:在容器内设置工作目录为 /opt/app。
  3. COPY go.* ./:将本地目录下的 go.mod 和 go.sum 文件复制到容器内的当前工作目录。
  4. RUN go mod download:在容器内运行 go mod download 命令,安装应用所需的依赖包。
  5. COPY . .:将当前目录下的所有文件和文件夹复制到容器内的当前工作目录。
  6. ARG LD_FLAGS='-linkmode external -w -extldflags "-static"':定义一个 LD_FLAGS 的构建参数。
  7. RUN go build -ldflags "$LD_FLAGS" -o example:在容器内运行 go build 命令,构建可执行二进制文件 example,并使用定义好的 LD_FLAGS。
  8. FROM alpine as runner:从官方的 Alpine 镜像中拉取一个最新版本,并将其命名为 runner。
  9. WORKDIR /opt/app:在容器内设置工作目录为 /opt/app。
  10. COPY --from=builder /opt/app/example /opt/app/example:从之前命名为 builder 的容器中,将构建好的可执行二进制文件 example 复制到当前容器。
  11. EXPOSE 8080:声明镜像运行时监听的端口号为 8080。
  12. CMD ["/opt/app/example"]:设置容器启动时执行的默认命令,即运行 /opt/app/example 可执行文件。
docker build -t golang .
docker run --publish 8080:8080 golang
root@node1:~# curl localhost:8080/hello
Hello World Golangroot@node1:~#

Node.js 应用容器化

root@node1:~/gitops/docker/13/node# ls -al
total 68
drwxr-xr-x  3 root root  4096 Feb 11 12:42 .
drwxr-xr-x  7 root root  4096 Feb 11 12:42 ..
-rw-r--r--  1 root root   230 Feb 11 12:42 app.js
-rw-r--r--  1 root root   589 Feb 11 12:42 Dockerfile
-rw-r--r--  1 root root    12 Feb 11 12:42 .dockerignore
drwxr-xr-x 59 root root  4096 Feb 11 12:42 node_modules
-rw-r--r--  1 root root   251 Feb 11 12:42 package.json
-rw-r--r--  1 root root 39326 Feb 11 12:42 package-lock.json
nano app.js
const express = require('express')
const app = express()
const port = 3000

app.get('/hello', (req, res) => {
  res.send('Hello World Node.js')
})

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

这个 Node.js 程序使用 Express 框架创建了一个简单的 HTTP 服务器,监听 3000 端口,并为 "/hello" 路径提供一个响应。

nano Dockerfile
# syntax=docker/dockerfile:1
FROM node:latest AS build
RUN sed -i "s@http://\(deb\|security\).debian.org@https://mirrors.aliyun.com@g" /etc/apt/sources.list
RUN apt-get update && apt-get install -y dumb-init
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm ci --only=production


FROM node:16.17.0-bullseye-slim

ENV NODE_ENV production
COPY --from=build /usr/bin/dumb-init /usr/bin/dumb-init
USER node
WORKDIR /usr/src/app
COPY --chown=node:node --from=build /usr/src/app/node_modules /usr/src/app/node_modules
COPY --chown=node:node . /usr/src/app
CMD ["dumb-init", "node", "app.js"]

这是一个 Dockerfile,用于构建一个 Node.js Web 应用的容器镜像。以下是对每条指令的解释:

  1. FROM node:latest AS build:从官方的 Node.js 镜像中拉取最新版本,并将其命名为 build。
  2. RUN sed -i "s@http://\(deb\|security\).debian.org@https://mirrors.aliyun.com@g" /etc/apt/sources.list:更换系统源为阿里云源。
  3. RUN apt-get update && apt-get install -y dumb-init:在容器内更新源并安装 dumb-init。
  4. WORKDIR /usr/src/app:在容器内设置工作目录为 /usr/src/app。
  5. COPY package*.json ./:将本地目录下以 package 开头并且后缀为 .json 的文件复制到容器内的当前工作目录。
  6. RUN npm ci --only=production:在容器内运行 npm ci 命令,只安装生产环境所需的依赖包。
  7. FROM node:16.17.0-bullseye-slim:从官方的 Node.js 镜像中拉取一个标记为 16.17.0-bullseye-slim 的版本。
  8. ENV NODE_ENV production:设置 NODE_ENV 环境变量为 production。
  9. COPY --from=build /usr/bin/dumb-init /usr/bin/dumb-init:从之前命名为 build 的容器复制 dumb-init 到当前容器。
  10. USER node:切换成 node 用户。
  11. WORKDIR /usr/src/app:在容器内设置工作目录为 /usr/src/app。
  12. COPY --chown=node:node --from=build /usr/src/app/node_modules /usr/src/app/node_modules:从之前命名为 build 的容器复制已经安装好的依赖包到当前容器。
  13. COPY --chown=node:node . /usr/src/app:将本地目录下的所有文件和文件夹复制到容器内的当前工作目录。
  14. CMD ["dumb-init", "node", "app.js"]:设置容器启动时执行的默认命令,即使用 dumb-init 启动 node 进程执行 app.js 文件。
docker build -t nodejs .
docker run --publish 3000:3000 nodejs
root@node1:~# curl localhost:3000/hello
Hello World Node.js

VUE 应用容器化: http-server 构建方式

root@node1:~/gitops/docker/13/vue/example# dir -al
total 96
drwxr-xr-x 5 root root  4096 Feb 11 12:42 .
drwxr-xr-x 3 root root  4096 Feb 11 12:42 ..
-rw-r--r-- 1 root root   202 Feb 11 12:42 Dockerfile
-rw-r--r-- 1 root root   290 Feb 11 12:42 Dockerfile-Nginx
-rw-r--r-- 1 root root    12 Feb 11 12:42 .dockerignore
-rw-r--r-- 1 root root   302 Feb 11 12:42 .gitignore
-rw-r--r-- 1 root root   337 Feb 11 12:42 index.html
-rw-r--r-- 1 root root   285 Feb 11 12:42 package.json
-rw-r--r-- 1 root root 44004 Feb 11 12:42 package-lock.json
drwxr-xr-x 2 root root  4096 Feb 11 12:42 public
-rw-r--r-- 1 root root   631 Feb 11 12:42 README.md
drwxr-xr-x 4 root root  4096 Feb 11 12:42 src
-rw-r--r-- 1 root root   300 Feb 11 12:42 vite.config.js
drwxr-xr-x 2 root root  4096 Feb 11 12:42 .vscode
nano Dockerfile
# syntax=docker/dockerfile:1

FROM node:lts-alpine
RUN npm install -g http-server
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

EXPOSE 8080
CMD [ "http-server", "dist" ]

这是一个 Dockerfile,用于构建一个 web 应用的容器镜像。以下是对每条指令的解释:

  1. FROM node:lts-alpine:从官方的 Node.js 镜像中拉取一个标记为 lts-alpine 的版本。
  2. RUN npm install -g http-server:在容器内全局安装 http-server 包。
  3. WORKDIR /app:在容器内设置工作目录为 /app。
  4. COPY package*.json ./:将本地目录下以 package 开头并且后缀为 .json 的文件复制到容器内的当前工作目录。
  5. RUN npm install:在容器内运行 npm install 命令,安装应用所需的依赖包。
  6. COPY . .:将当前目录下的所有文件和文件夹(除了前面已经复制的 package.json 文件)复制到容器内的当前工作目录。
  7. RUN npm run build:在容器内运行 npm run build 命令,构建生产环境的应用代码。
  8. EXPOSE 8080:声明镜像运行时监听的端口号为 8080。
  9. CMD [ "http-server", "dist" ]:设置容器启动时执行的默认命令,即以当前工作目录下的 dist 目录为根目录启动 http-server。
docker build -t vue .
docker run --publish 8080:8080 vue

打开浏览器访问 http://localhost:8080 ,如果出现 Vue 示例应用的项目,说明镜像构建完成

VUE 应用容器化:Nginx 构建方式

nano Dockerfile-Nginx
# syntax=docker/dockerfile:1

FROM node:lts-alpine as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

这是一个 Dockerfile,用于构建一个 web 应用的容器镜像。以下是对每条指令的解释:

  1. FROM node:lts-alpine as build-stage:从官方的 Node.js 镜像中拉取一个标记为 lts-alpine 的版本,并将其命名为 build-stage。
  2. WORKDIR /app:在容器内设置工作目录为 /app。
  3. COPY package*.json ./:将本地目录下以 package 开头并且后缀为 .json 的文件复制到容器内的当前工作目录。
  4. RUN npm install:在容器内运行 npm install 命令,安装应用所需的依赖包。
  5. COPY . .:将当前目录下的所有文件和文件夹(除了前面已经复制的 package.json 文件)复制到容器内的当前工作目录。
  6. RUN npm run build:在容器内运行 npm run build 命令,构建生产环境的应用代码。
  7. FROM nginx:stable-alpine as production-stage:从官方的 Nginx 镜像中拉取一个标记为 stable-alpine 的版本,并将其命名为 production-stage。
  8. COPY --from=build-stage /app/dist /usr/share/nginx/html:从之前命名为 build-stage 的容器复制构建好的代码到 Nginx 安装目录下的 html 文件夹中。
  9. EXPOSE 80:声明镜像运行时监听的端口号为 80。
  10. CMD ["nginx", "-g", "daemon off;"]:设置容器启动时执行的默认命令,即运行 Nginx 并以 daemon 形式运行。
docker build -t vue-nginx -f Dockerfile-Nginx .
docker run --publish 8080:80 vue-nginx

打开浏览器访问 http://localhost:8080 验证一下,如果出现和前面提到的 http-server 构建方式一样的 Vue 示例应用界面,就说明镜像构建成功了。

构建多平台镜像 (使用docker desktop演示)

确认experiment功能开启之后,创建构建器

docker buildx create --name builder

设置为默认构建器

docker buildx use builder

初始化构建器,这一步主要是启动 buildkit 容器

docker buildx inspect --bootstrap
chengzh@TB15P:~$ docker buildx inspect --bootstrap
[+] Building 28.0s (1/1) FINISHED
 => [internal] booting buildkit                                                                                                                                               28.0s
 => => pulling image moby/buildkit:buildx-stable-1                                                                                                                            23.8s
 => => creating container buildx_buildkit_builder0                                                                                                                             4.2s
Name:   builder
Driver: docker-container

Nodes:
Name:      builder0
Endpoint:  unix:///var/run/docker.sock
Status:    running
Platforms: linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6
git clone https://github.com/cloudzun/gitops
cd gitops/docker/13/multi-arch
root@node1:~/gitops/docker/13/multi-arch# ls -al
total 28
drwxr-xr-x 2 root root 4096 Feb 11 12:42 .
drwxr-xr-x 7 root root 4096 Feb 11 12:42 ..
-rw-r--r-- 1 root root  389 Feb 11 12:42 Dockerfile
-rw-r--r-- 1 root root 1075 Feb 11 12:42 go.mod
-rw-r--r-- 1 root root 6962 Feb 11 12:42 go.sum
-rw-r--r-- 1 root root  397 Feb 11 12:42 main.go
nano main.go
package main
import (
    "net/http"
    "runtime"
    "github.com/gin-gonic/gin"
)
var (
    r = gin.Default()
)
func main() {
    r.GET("/", indexHandler)
    r.Run(":8080")
}
func indexHandler(c *gin.Context) {
    var osinfo = map[string]string{
        "arch":    runtime.GOARCH,
        "os":      runtime.GOOS,
        "version": runtime.Version(),
    }
    c.JSON(http.StatusOK, osinfo)
}

这是使用 Go 语言和 Gin 框架创建的 HTTP 服务器,它会在根路径 "/" 处接收 GET 请求并调用名为 indexHandler 的函数进行处理。

indexHandler 函数使用 runtime 包来获取当前操作系统的信息,并将其存储在一个 map[string]string 变量 osinfo 中。然后使用 gin.Context 结构体的 JSON 方法将 osinfo 变量编码成 JSON 格式并作为 HTTP 响应返回给客户端。

最后,该服务器会在本地主机的 8080 端口上运行,等待请求的到来

nano Dockerfile
# syntax=docker/dockerfile:1
FROM --platform=$BUILDPLATFORM golang:1.18 as build
ARG TARGETOS TARGETARCH
WORKDIR /opt/app
COPY go.* ./
RUN go mod download
COPY . .
RUN --mount=type=cache,target=/root/.cache/go-build \
GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /opt/app/example .

FROM ubuntu:latest
WORKDIR /opt/app
COPY --from=build /opt/app/example ./example
CMD ["/opt/app/example

这是一个 Dockerfile,用于构建一个 GO 应用的容器镜像。以下是对每条指令的解释:

  1. FROM --platform=$BUILDPLATFORM golang:1.18 as build:从官方的 Golang 镜像中拉取一个标记为 1.18 的版本,并将其命名为 build。
  2. ARG TARGETOS TARGETARCH:定义两个构建参数,分别为目标系统和架构。
  3. WORKDIR /opt/app:在容器内设置工作目录为 /opt/app。
  4. COPY go.* ./:将本地目录下的 go.mod 和 go.sum 文件复制到容器内的当前工作目录。
  5. RUN go mod download:在容器内运行 go mod download 命令,安装应用所需的依赖包。
  6. COPY . .:将当前目录下的所有文件和文件夹复制到容器内的当前工作目录。
  7. RUN --mount=type=cache,target=/root/.cache/go-build \ GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /opt/app/example .:使用 --mount 标志来将 go-build 缓存挂载到容器中,然后在容器中使用环境变量 $TARGETOS$TARGETARCH 构建应用程序,并将其输出到容器内的 /opt/app/example 文件中。
  8. FROM ubuntu:latest:从官方的 Ubuntu 镜像中拉取最新版本。
  9. WORKDIR /opt/app:在容器内设置工作目录为 /opt/app。
  10. COPY --from=build /opt/app/example ./example:从之前命名为 build 的容器中,将构建好的可执行二进制文件 example 复制到当前容器内的 ./example 目录中。
  11. CMD ["/opt/app/example"]:设置容器启动时执行的默认命令,即运行 /opt/app/example 可执行文件。

登录到容器镜像库,默认是docker hub

docker login

构建多平台镜像

docker buildx build --platform linux/amd64,linux/arm64 -t chengzh/multi-arch:latest --push  .

缩减镜像体积

基本构建

git clone https://github.com/cloudzun/gitops
cd gitops/docker/13/golang
dir -al
root@node1:~/gitops/docker/13/golang# dir -al
total 6612
drwxr-xr-x 2 root root    4096 Feb 11 13:39 .
drwxr-xr-x 7 root root    4096 Feb 11 12:42 ..
-rw-r--r-- 1 root root     354 Feb 11 12:42 Dockerfile
-rw-r--r-- 1 root root     120 Feb 11 12:42 Dockerfile-1
-rw-r--r-- 1 root root     127 Feb 11 12:42 Dockerfile-2
-rw-r--r-- 1 root root     105 Feb 11 12:42 Dockerfile-3
-rw-r--r-- 1 root root     279 Feb 11 12:42 Dockerfile-4
-rw-r--r-- 1 root root     286 Feb 11 12:42 Dockerfile-5
-rw-r--r-- 1 root root     287 Feb 11 12:42 Dockerfile-6
-rw-r--r-- 1 root root     279 Feb 11 12:42 Dockerfile-7
-rwxr-xr-x 1 root root 6716498 Feb 11 12:42 example
-rw-r--r-- 1 root root     599 Feb 11 12:42 go.mod
-rw-r--r-- 1 root root    2825 Feb 11 12:42 go.sum
-rw-r--r-- 1 root root     240 Feb 11 12:42 main.go

Dockerfile-1

# syntax=docker/dockerfile:1
FROM golang:1.17
WORKDIR /opt/app
COPY . .
RUN go build -o example
CMD ["/opt/app/example"]

这个 Dockerfile 描述的构建过程非常简单,我们首选 Golang:1.17 版本的镜像作为编译环境,将源码拷贝到镜像中,然后运行 go build 编译源码生成二进制可执行文件,最后配置启动命令。

docker build -t golang:1 -f Dockerfile-1 .

查看映像

docker images

查看映像历史

docker history golang:1
root@node1:~/gitops/docker/13/golang# docker images
REPOSITORY   TAG       IMAGE ID       CREATED              SIZE
golang       1         ddb59fef2a33   12 seconds ago   1.04GB
golang       1.17      742df529b073   7 months ago     942MB
root@node1:~/gitops/docker/13/golang# docker history golang:1
IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
74dbc784726c   12 minutes ago   /bin/sh -c #(nop)  CMD ["/opt/app/example"]     0B
f32a98328157   12 minutes ago   /bin/sh -c go build -o example                  90.3MB
09aefcaec12f   12 minutes ago   /bin/sh -c #(nop) COPY dir:f9adead7af783e6c7…   6.72MB
df00d7d44056   12 minutes ago   /bin/sh -c #(nop) WORKDIR /opt/app              0B
276895edf967   13 months ago    /bin/sh -c #(nop) WORKDIR /go                   0B
<missing>      13 months ago    /bin/sh -c mkdir -p "$GOPATH/src" "$GOPATH/b…   0B
<missing>      13 months ago    /bin/sh -c #(nop)  ENV PATH=/go/bin:/usr/loc…   0B
<missing>      13 months ago    /bin/sh -c #(nop)  ENV GOPATH=/go               0B
<missing>      13 months ago    /bin/sh -c set -eux;  arch="$(dpkg --print-a…   408MB
<missing>      13 months ago    /bin/sh -c #(nop)  ENV GOLANG_VERSION=1.17.5    0B
<missing>      13 months ago    /bin/sh -c #(nop)  ENV PATH=/usr/local/go/bi…   0B
<missing>      13 months ago    /bin/sh -c set -eux;  apt-get update;  apt-g…   227MB
<missing>      13 months ago    /bin/sh -c apt-get update && apt-get install…   152MB
<missing>      13 months ago    /bin/sh -c set -ex;  if ! command -v gpg > /…   18.9MB
<missing>      13 months ago    /bin/sh -c set -eux;  apt-get update;  apt-g…   10.7MB
<missing>      13 months ago    /bin/sh -c #(nop)  CMD ["bash"]                 0B
<missing>      13 months ago    /bin/sh -c #(nop) ADD file:c03517c5ddbed4053…   124MB

从返回的结果来看,这个 Dockerfile 构建的镜像大小非常惊人,Golang 示例程序使用 go build 命令编译后,二进制可执行文件大约 6M 左右,但容器化之后,镜像达到 900M,显然我们需要进一步优化镜像大小。

  1. 74dbc784726c - 创建于12分钟前,用于设置默认命令。CMD ["/opt/app/example"] 指定了当容器运行时将执行的命令。
  2. f32a98328157 - 创建于12分钟前,用于构建Go应用程序。go build -o example 会编译Go代码并输出一个名为example的可执行文件。
  3. 09aefcaec12f - 创建于12分钟前,复制文件到镜像。COPY dir:f9adead7af783e6c7… 将当前目录下的文件复制到镜像中。
  4. df00d7d44056 - 创建于12分钟前,设置工作目录。WORKDIR /opt/app 指定了在容器内执行命令时使用的默认工作目录。
  5. 276895edf967 - 创建于13个月前,设置工作目录。WORKDIR /go 是旧的Go工作目录,已被废弃但仍存在于这个镜像中。
  6. <missing> - 创建于13个月前,创建目录。这个层创建了 "GitOps 实操手册1:应用程序容器化GOPATH/bin" 两个目录。
  7. <missing> - 创建于13个月前,设置环境变量。ENV PATH=/go/bin:/usr/loc… 设置了PATH环境变量,将Go的bin目录加入到PATH中。
  8. <missing> - 创建于13个月前,设置环境变量。ENV GOPATH=/go 设置了Go的GOPATH环境变量。
  9. <missing> - 创建于13个月前,安装Go编程语言。这一层用于下载和安装Go编程语言的特定版本。
  10. <missing> - 创建于13个月前,设置环境变量。ENV GOLANG_VERSION=1.17.5 设置了Go的版本。
  11. <missing> - 创建于13个月前,设置环境变量。ENV PATH=/usr/local/go/bi… 将Go的bin目录加入到PATH中。
  12. <missing> - 创建于13个月前,更新软件包和安装依赖。这一层用于更新Debian软件包并安装一些必要的依赖。
  13. <missing> - 创建于13个月前,更新软件包和安装依赖。这一层用于更新Debian软件包并安装一些必要的依赖。
  14. <missing> - 创建于13个月前,安装GPG。这一层用于安装GPG工具,用于验证下载文件的签名。
  15. <missing> - 创建于13个月前,更新软件包和安装依赖。这一层用于更新Debian软件包并安装一些必要的依赖。
  16. <missing> - 创建于13个月前,设置默认命令。CMD ["bash"] 指定了当容器运行时,如果没有其他命令被指定,将执行bash
  17. <missing> - 创建于13个月前,添加文件到镜像。ADD file:c03517c5ddbed4053… 这一层将一个文件(很可能是基础镜像所需的文件系统)添加到镜像中。这是构建这个Golang镜像的基础层,通常基于某个Linux发行版,如Debian或Alpine。

总结:这个Docker镜像是基于Golang编程语言构建的。它包含了Go编程环境以及一些必要的依赖。这个镜像的主要目的是为了编译和运行Go应用程序。在这个镜像的基础上,有一些额外的层被添加,用于设置工作目录,复制文件,构建Go应用程序,以及设置默认的运行命令。当你运行一个基于这个镜像的容器时,它会执行/opt/app/example作为默认命令

替换镜像

Dockerfile-2

# syntax=docker/dockerfile:1
FROM golang:1.17-alpine
WORKDIR /opt/app
COPY . .
RUN go build -o example
CMD ["/opt/app/example"]

将 Golang:1.17 基础镜像替换为 golang:1.17-alpine 版本,一般来说,Alpine 版本的镜像相比较普通镜像来说删除了一些非必需的系统应用,所以镜像体积更小

docker build -t golang:2 -f Dockerfile-2 .

查看映像

docker images

查看映像历史

docker history golang:2
root@node1:~/gitops/docker/13/golang# docker images
REPOSITORY   TAG           IMAGE ID       CREATED          SIZE
golang       2             bfe63d3346bb   31 seconds ago   411MB
golang       1             ddb59fef2a33   6 minutes ago    1.04GB
golang       1.17-alpine   270c4f58750f   7 months ago     314MB
golang       1.17          742df529b073   7 months ago     942MB
root@node1:~/gitops/docker/13/golang# docker history golang:2
IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
96de41a9c9f3   28 minutes ago   /bin/sh -c #(nop)  CMD ["/opt/app/example"]     0B
40318a9af90b   28 minutes ago   /bin/sh -c go build -o example                  90.3MB
0bf118996932   29 minutes ago   /bin/sh -c #(nop) COPY dir:f9adead7af783e6c7…   6.72MB
d093f960daa8   29 minutes ago   /bin/sh -c #(nop) WORKDIR /opt/app              0B
d8bf44a3f6b4   14 months ago    /bin/sh -c #(nop) WORKDIR /go                   0B
<missing>      14 months ago    /bin/sh -c mkdir -p "$GOPATH/src" "$GOPATH/b…   0B
<missing>      14 months ago    /bin/sh -c #(nop)  ENV PATH=/go/bin:/usr/loc…   0B
<missing>      14 months ago    /bin/sh -c #(nop)  ENV GOPATH=/go               0B
<missing>      14 months ago    /bin/sh -c set -eux;  apk add --no-cache --v…   309MB
<missing>      14 months ago    /bin/sh -c #(nop)  ENV GOLANG_VERSION=1.17.5    0B
<missing>      14 months ago    /bin/sh -c #(nop)  ENV PATH=/usr/local/go/bi…   0B
<missing>      14 months ago    /bin/sh -c [ ! -e /etc/nsswitch.conf ] && ec…   17B
<missing>      14 months ago    /bin/sh -c apk add --no-cache ca-certificates   519kB
<missing>      14 months ago    /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B
<missing>      14 months ago    /bin/sh -c #(nop) ADD file:9233f6f2237d79659…   5.59MB

本地编译

Dockerfile-3

在本地先编译出可执行文件,再将它复制到一个更小体积的 ubuntu 镜像内,直接引入了不包含 Golang 编译工具的 ubuntu 镜像作为基础运行环境,接下来使用 docker build 命令构建镜像

CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o example .
root@node1:~/gitops/docker/13/golang# CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o example .
go: downloading github.com/labstack/echo/v4 v4.9.0
go: downloading github.com/labstack/gommon v0.3.1
go: downloading golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
go: downloading golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f
go: downloading github.com/mattn/go-colorable v0.1.11
go: downloading github.com/mattn/go-isatty v0.0.14
go: downloading github.com/valyala/fasttemplate v1.2.1
go: downloading golang.org/x/sys v0.0.0-20211103235746-7861aae1554b
go: downloading github.com/valyala/bytebufferpool v1.0.0
go: downloading golang.org/x/text v0.3.7
# syntax=docker/dockerfile:1
FROM ubuntu:latest
WORKDIR /opt/app
COPY example ./
CMD ["/opt/app/example"]
docker build -t golang:3 -f Dockerfile-3 .

查看映像

docker images

查看映像历史

docker history golang:3
root@node1:~/gitops/docker/13/golang# docker images
REPOSITORY   TAG           IMAGE ID       CREATED          SIZE
golang       3             27414418a1cb   30 seconds ago   84.8MB
golang       2             bfe63d3346bb   2 minutes ago    411MB
golang       1             ddb59fef2a33   9 minutes ago    1.04GB
ubuntu       latest        74f2314a03de   13 days ago      77.8MB
golang       1.17-alpine   270c4f58750f   7 months ago     314MB
golang       1.17          742df529b073   7 months ago     942MB
root@node1:~/gitops/docker/13/golang# docker history golang:3
IMAGE          CREATED              CREATED BY                                      SIZE      COMMENT
27414418a1cb   About a minute ago   /bin/sh -c #(nop)  CMD ["/opt/app/example"]     0B        
e4e0c73fee4e   About a minute ago   /bin/sh -c #(nop) COPY file:20d600ca265b298d…   6.98MB    
0398b920df19   About a minute ago   /bin/sh -c #(nop) WORKDIR /opt/app              0B        
74f2314a03de   13 days ago          /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B        
<missing>      13 days ago          /bin/sh -c #(nop) ADD file:fb4c8244f4468cdd3…   77.8MB    
<missing>      13 days ago          /bin/sh -c #(nop)  LABEL org.opencontainers.…   0B        
<missing>      13 days ago          /bin/sh -c #(nop)  LABEL org.opencontainers.…   0B        
<missing>      13 days ago          /bin/sh -c #(nop)  ARG LAUNCHPAD_BUILD_ARCH     0B        
<missing>      13 days ago          /bin/sh -c #(nop)  ARG RELEASE                  0B

以下是对这个输出的详细解释:

  1. 27414418a1cb - 创建于大约1分钟前,用于设置默认命令。CMD ["/opt/app/example"] 指定了当容器运行时将执行的命令。
  2. e4e0c73fee4e - 创建于大约1分钟前,复制文件到镜像。COPY file:20d600ca265b298d… 将指定文件复制到镜像中。
  3. 0398b920df19 - 创建于大约1分钟前,设置工作目录。WORKDIR /opt/app 指定了在容器内执行命令时使用的默认工作目录。
  4. 74f2314a03de - 创建于13天前,设置默认命令。CMD ["/bin/bash"] 指定了当容器运行时,如果没有其他命令被指定,将执行/bin/bash
  5. <missing> - 创建于13天前,添加文件到镜像。ADD file:fb4c8244f4468cdd3… 这一层将一个文件(很可能是基础镜像所需的文件系统)添加到镜像中。这是构建这个Golang镜像的基础层,通常基于某个Linux发行版,如Debian或Alpine。
  6. <missing> - 创建于13天前,设置标签。LABEL org.opencontainers… 设置了一个标签,提供有关镜像的元数据。
  7. <missing> - 创建于13天前,设置标签。LABEL org.opencontainers… 设置了另一个标签,提供有关镜像的元数据。
  8. <missing> - 创建于13天前,设置构建参数。ARG LAUNCHPAD_BUILD_ARCH 设置了一个构建参数,用于指定构建时的体系结构。
  9. <missing> - 创建于13天前,设置构建参数。ARG RELEASE 设置了一个构建参数,用于指定构建时的Golang版本。

总结:这个Docker镜像也是基于Golang编程语言构建的。它包含了Go编程环境以及一些必要的依赖。在这个镜像的基础上,有一些额外的层被添加,用于设置工作目录,复制文件,以及设置默认的运行命令。当你运行一个基于这个镜像的容器时,它会执行/opt/app/example作为默认命令。与前一个镜像相比,这个镜像具有更少的层次和更简洁的构建历史。

从返回内容可以看出,这种构建方式生成的镜像只有 84.8M,在体积上比最初的 1.04G 缩小了 90% 。镜像的最终大小就相当于 ubuntu:latest 的大小加上 Golang 二进制可执行文件的大小。

不过,这种方式将应用的编译过程拆分到了宿主机上,这会让 Dockerfile 失去描述应用编译和打包的作用,不是一个好的实践

多阶段构建

多阶段构建的本质其实就是将镜像构建过程拆分成编译过程和运行过程。

  • 第一个阶段对应编译的过程,负责生成可执行文件;
  • 第二个阶段对应运行过程,也就是拷贝第一阶段的二进制可执行文件,并为程序提供运行环境,最终镜像也就是第二阶段生成的镜像。

Dockerfile-4

# syntax=docker/dockerfile:1

# Step 1: build golang binary
FROM golang:1.17 as builder
WORKDIR /opt/app
COPY . .
RUN go build -o example

# Step 2: copy binary from step1
FROM ubuntu:latest
WORKDIR /opt/app
COPY --from=builder /opt/app/example ./example
CMD ["/opt/app/example"]

多阶段构建其实就是将 Dockerfile-1 和 Dockerfile-3 的内容进行合并重组

这段内容里有两个 FROM 语句,所以这是一个包含两个阶段的构建过程。

第一个阶段是从第 4 行至第 7 行,它的作用是编译生成二进制可执行文件,就像我们之前在本地执行的编译操作一样。

第二阶段在第 10 行到 13 行,它的作用是将第一阶段生成的二进制可执行文件复制到当前阶段,把 ubuntu:latest 作为运行环境,并设置 CMD 启动命令。

docker build -t golang:4 -f Dockerfile-4 .

查看映像

docker images

查看映像历史

docker history golang:4
root@node1:~/gitops/docker/13/golang# docker images
REPOSITORY   TAG           IMAGE ID       CREATED          SIZE
golang       4             54c92336ac1c   30 seconds ago   84.7MB
<none>       <none>        c564c307d3c2   31 seconds ago   1.04GB
golang       3             27414418a1cb   3 minutes ago    84.8MB
golang       2             bfe63d3346bb   5 minutes ago    411MB
golang       1             ddb59fef2a33   11 minutes ago   1.04GB
ubuntu       latest        74f2314a03de   13 days ago      77.8MB
golang       1.17-alpine   270c4f58750f   7 months ago     314MB
golang       1.17          742df529b073   7 months ago     942MB
root@node1:~/gitops/docker/13/golang# docker history golang:4
IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
54c92336ac1c   59 seconds ago   /bin/sh -c #(nop)  CMD ["/opt/app/example"]     0B        
138f936e2b64   59 seconds ago   /bin/sh -c #(nop) COPY file:2bd00ab8d3308a9a…   6.88MB    
0398b920df19   3 minutes ago    /bin/sh -c #(nop) WORKDIR /opt/app              0B        
74f2314a03de   13 days ago      /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B        
<missing>      13 days ago      /bin/sh -c #(nop) ADD file:fb4c8244f4468cdd3…   77.8MB    
<missing>      13 days ago      /bin/sh -c #(nop)  LABEL org.opencontainers.…   0B        
<missing>      13 days ago      /bin/sh -c #(nop)  LABEL org.opencontainers.…   0B        
<missing>      13 days ago      /bin/sh -c #(nop)  ARG LAUNCHPAD_BUILD_ARCH     0B        
<missing>      13 days ago      /bin/sh -c #(nop)  ARG RELEASE                  0B

进一步压缩

Dockerfile-5

# syntax=docker/dockerfile:1

# Step 1: build golang binary
FROM golang:1.17 as builder
WORKDIR /opt/app
COPY . .
RUN CGO_ENABLED=0 go build -o example

# Step 2: copy binary from step1
FROM alpine
WORKDIR /opt/app
COPY --from=builder /opt/app/example ./example
CMD ["/opt/app/example"]

要进一步缩小体积,我们可以继续使用其他更小的镜像作为第二阶段的运行镜像,这就要说到 Alpine 了。

Alpine 镜像是专门为容器化定制的 Linux 发行版,它的最大特点是体积非常小。现在,我们尝试使用它,将第二阶段构建的镜像替换为 Alpine 镜像

docker build -t golang:5 -f Dockerfile-5 .

查看映像

docker images

查看映像历史

docker history golang:5
root@node1:~/gitops/docker/13/golang# docker images
REPOSITORY   TAG           IMAGE ID       CREATED          SIZE
golang       5             419b26609935   31 seconds ago   13.9MB
<none>       <none>        a4de3e3e3d49   34 seconds ago   1.05GB
golang       4             54c92336ac1c   2 minutes ago    84.7MB
<none>       <none>        c564c307d3c2   2 minutes ago    1.04GB
golang       3             27414418a1cb   5 minutes ago    84.8MB
golang       2             bfe63d3346bb   7 minutes ago    411MB
golang       1             ddb59fef2a33   13 minutes ago   1.04GB
ubuntu       latest        74f2314a03de   13 days ago      77.8MB
alpine       latest        b2aa39c304c2   4 weeks ago      7.05MB
golang       1.17-alpine   270c4f58750f   7 months ago     314MB
golang       1.17          742df529b073   7 months ago     942MB
root@node1:~/gitops/docker/13/golang# docker history golang:5
IMAGE          CREATED              CREATED BY                                      SIZE      COMMENT
419b26609935   About a minute ago   /bin/sh -c #(nop)  CMD ["/opt/app/example"]     0B        
f98d161a91f7   About a minute ago   /bin/sh -c #(nop) COPY file:767cca1c08627393…   6.82MB    
4a7f1f27187d   About a minute ago   /bin/sh -c #(nop) WORKDIR /opt/app              0B        
b2aa39c304c2   4 weeks ago          /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B        
<missing>      4 weeks ago          /bin/sh -c #(nop) ADD file:40887ab7c06977737…   7.05MB

以下是对这个输出的详细解释:

  1. 419b26609935 - 创建于大约1分钟前,用于设置默认命令。CMD ["/opt/app/example"] 指定了当容器运行时将执行的命令。
  2. f98d161a91f7 - 创建于大约1分钟前,复制文件到镜像。COPY file:767cca1c08627393… 将指定文件复制到镜像中。
  3. 4a7f1f27187d - 创建于大约1分钟前,设置工作目录。WORKDIR /opt/app 指定了在容器内执行命令时使用的默认工作目录。
  4. b2aa39c304c2 - 创建于4周前,设置默认命令。CMD ["/bin/sh"] 指定了当容器运行时,如果没有其他命令被指定,将执行/bin/sh
  5. <missing> - 创建于4周前,添加文件到镜像。ADD file:40887ab7c06977737… 这一层将一个文件(很可能是基础镜像所需的文件系统)添加到镜像中。这是构建这个Golang镜像的基础层,通常基于某个Linux发行版,如Debian或Alpine。

总结:这个Docker镜像也是基于Golang编程语言构建的。它包含了Go编程环境以及一些必要的依赖。在这个镜像的基础上,有一些额外的层被添加,用于设置工作目录,复制文件,以及设置默认的运行命令。当你运行一个基于这个镜像的容器时,它会执行/opt/app/example作为默认命令。这个镜像的层次和构建历史比之前镜像更简洁,只包含了必要的层。

由于 Alpine 镜像和常规 Linux 发行版存在一些差异,通常并不推荐在生产环境下使用 Alpine 镜像作为业务的运行镜像

极限压缩

把第二个阶段的镜像替换为一个“空镜像”,这个空镜像称为 scratch 镜像,我们将 Dockerfile-4 第二阶段的构建替换为 scratch 镜像

Dockerfile-6

# syntax=docker/dockerfile:1

# Step 1: build golang binary
FROM golang:1.17 as builder
WORKDIR /opt/app
COPY . .
RUN CGO_ENABLED=0 go build -o example

# Step 2: copy binary from step1
FROM scratch
WORKDIR /opt/app
COPY --from=builder /opt/app/example ./example
CMD ["/opt/app/example"]

由于 scratch 镜像不包含任何内容,所以我们在编译 Golang 可执行文件的时候禁用了 CGO,这样才能让编译出来的程序在 scratch 镜像中运行

docker build -t golang:6 -f Dockerfile-6 .

查看映像

docker images

查看映像历史

docker history golang:6
root@node1:~/gitops/docker/13/golang# docker images
REPOSITORY   TAG           IMAGE ID       CREATED          SIZE
golang       6             d05c2b689d37   33 seconds ago   6.82MB
golang       5             419b26609935   2 minutes ago    13.9MB
<none>       <none>        a4de3e3e3d49   2 minutes ago    1.05GB
golang       4             54c92336ac1c   3 minutes ago    84.7MB
<none>       <none>        c564c307d3c2   3 minutes ago    1.04GB
golang       3             27414418a1cb   6 minutes ago    84.8MB
golang       2             bfe63d3346bb   9 minutes ago    411MB
golang       1             ddb59fef2a33   15 minutes ago   1.04GB
ubuntu       latest        74f2314a03de   13 days ago      77.8MB
alpine       latest        b2aa39c304c2   4 weeks ago      7.05MB
golang       1.17-alpine   270c4f58750f   7 months ago     314MB
golang       1.17          742df529b073   7 months ago     942MB
root@node1:~/gitops/docker/13/golang# docker history golang:6
IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
d05c2b689d37   57 seconds ago   /bin/sh -c #(nop)  CMD ["/opt/app/example"]     0B        
26a048c3fc5b   58 seconds ago   /bin/sh -c #(nop) COPY file:767cca1c08627393…   6.82MB    
b3cfc770d142   58 seconds ago   /bin/sh -c #(nop) WORKDIR /opt/app              0B

以下是对这个输出的详细解释:

  1. d05c2b689d37 - 创建于57秒前,用于设置默认命令。CMD ["/opt/app/example"] 指定了当容器运行时将执行的命令。
  2. 26a048c3fc5b - 创建于58秒前,复制文件到镜像。COPY file:767cca1c08627393… 将指定文件复制到镜像中。
  3. b3cfc770d142 - 创建于58秒前,设置工作目录。WORKDIR /opt/app 指定了在容器内执行命令时使用的默认工作目录。

总结:这个Docker镜像也是基于Golang编程语言构建的。然而,与之前的镜像相比,这个镜像的层次和构建历史更加简洁。它仅包含了设置工作目录,复制文件,以及设置默认的运行命令这三个层。当你运行一个基于这个镜像的容器时,它会执行/opt/app/example作为默认命令。注意,这个镜像可能不包含完整的Golang编程环境,因此在使用这个镜像时需要注意确保环境的兼容性。

scratch 镜像是一个空白镜像,甚至连 shell 都没有,所以也无法进入容器查看文件或进行调试。在生产环境中,如果对安全有极高的要求,可以考虑把 scratch 作为程序的运行镜像。

复用构建缓存,加快构建过程

要使用 Golang 依赖的缓存,最简单的办法是:先复制依赖文件,再下载依赖,最后再复制源码进行编译。

Dockerfile-7

# syntax=docker/dockerfile:1

# Step 1: build golang binary
FROM golang:1.17 as builder
WORKDIR /opt/app
COPY go.* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o example

# Step 2: copy binary from step1
FROM scratch
WORKDIR /opt/app
COPY --from=builder /opt/app/example ./example
CMD ["/opt/app/example"]

这样,在每次代码变更而依赖不变的情况下,Docker 都会复用第 4 行和第 5 行产生的构建缓存,这可以加速镜像构建过程

docker build -t golang:7 -f Dockerfile-7 .

查看映像

docker images

查看映像历史

docker history golang:7
root@node1:~/gitops/docker/13/golang# docker images
REPOSITORY   TAG           IMAGE ID       CREATED              SIZE
golang       7             990cb70181c3   24 seconds ago   13.9MB
<none>       <none>        4d777e500587   25 seconds ago   1.05GB
golang       6             d05c2b689d37   5 minutes ago    6.82MB
golang       5             419b26609935   7 minutes ago    13.9MB
<none>       <none>        a4de3e3e3d49   7 minutes ago    1.05GB
golang       4             54c92336ac1c   9 minutes ago    84.7MB
<none>       <none>        c564c307d3c2   9 minutes ago    1.04GB
golang       3             27414418a1cb   12 minutes ago   84.8MB
golang       2             bfe63d3346bb   14 minutes ago   411MB
golang       1             ddb59fef2a33   20 minutes ago   1.04GB
ubuntu       latest        74f2314a03de   13 days ago      77.8MB
alpine       latest        b2aa39c304c2   4 weeks ago      7.05MB
golang       1.17-alpine   270c4f58750f   7 months ago     314MB
golang       1.17          742df529b073   7 months ago     942MB
root@node1:~/gitops/docker/13/golang# docker history golang:7
IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
990cb70181c3   14 seconds ago   /bin/sh -c #(nop)  CMD ["/opt/app/example"]     0B        
abf475bd2ed2   15 seconds ago   /bin/sh -c #(nop) COPY file:2bd00ab8d3308a9a…   6.88MB    
4a7f1f27187d   7 minutes ago    /bin/sh -c #(nop) WORKDIR /opt/app              0B        
b2aa39c304c2   4 weeks ago      /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B        
<missing>      4 weeks ago      /bin/sh -c #(nop) ADD file:40887ab7c06977737…   7.05MB

使用Github Action 自动构建映像

定义 build.yaml

示例repo:https://github.com/cloudzun/kubernetes-example/tree/main/.github/workflows

name: build

on:
  push:
    branches:
      - 'main'

env:
  DOCKERHUB_USERNAME: cloudzun  # 替换为自己的docker hub 账号

jobs:
  docker:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Set outputs
        id: vars
        run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v2
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2
      - name: Login to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ env.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      - name: Build backend and push
        uses: docker/build-push-action@v3
        with:
          context: backend
          push: true
          tags: ${{ env.DOCKERHUB_USERNAME }}/backend:${{ steps.vars.outputs.sha_short }}
      - name: Build frontend and push
        uses: docker/build-push-action@v3
        with:
          context: frontend
          push: true
          tags: ${{ env.DOCKERHUB_USERNAME }}/frontend:${{ steps.vars.outputs.sha_short }}

这是一个GitHub Actions的工作流程配置文件,用于在推送到main分支时自动构建并推送Docker镜像到Docker Hub。以下是详细解释:

  1. name: build:为此GitHub Actions工作流程命名为"build"。
  2. on:指定触发此工作流程的条件。
  • push:当有新的推送事件时触发。
  • branches:指定仅在推送到main分支时触发此工作流程。
  1. env:设置环境变量。
  • DOCKERHUB_USERNAME: cloudzun:设置Docker Hub用户名为"cloudzun"。请将此值替换为你自己的Docker Hub用户名。
  1. jobs:定义工作流程中的任务。
  • docker:定义一个名为"docker"的任务。
  • runs-on: ubuntu-latest:指定任务运行在最新版的Ubuntu虚拟环境上。
  1. steps:定义任务中的步骤。
  • Checkout:使用actions/checkout@v3 action,将代码库检出到虚拟环境中。
  • Set outputs:设置输出变量。使用git rev-parse --short HEAD命令获取当前提交的sha_short值,并将其存储为输出变量sha_short
  • Set up QEMU:使用docker/setup-qemu-action@v2 action,设置QEMU模拟器,以支持跨平台的Docker镜像构建。
  • Set up Docker Buildx:使用docker/setup-buildx-action@v2 action,设置Docker Buildx,它是一个Docker CLI插件,用于扩展原生的Docker构建功能。
  • Login to Docker Hub:使用docker/login-action@v2 action,登录到Docker Hub。使用环境变量DOCKERHUB_USERNAME和GitHub仓库的密钥DOCKERHUB_TOKEN进行身份验证。
  • Build backend and push:使用docker/build-push-action@v3 action,构建名为"backend"的Docker镜像并推送到Docker Hub。将镜像的标签设置为用户名/backend:sha_short
  • Build frontend and push:使用docker/build-push-action@v3 action,构建名为"frontend"的Docker镜像并推送到Docker Hub。将镜像的标签设置为用户名/frontend:sha_short

在这个工作流中,这 7 个阶段会具体执行下面几件事。 当有新的提交推送到main分支时,这个GitHub Actions工作流会自动执行以下操作:

  1. 检出仓库:将Git仓库的源代码检出到GitHub Actions运行的虚拟环境中。
  2. 获取当前提交的短SHA值:获取当前推送事件对应的提交的短SHA值(sha_short),并将其存储为输出变量,用于后续步骤中构建Docker镜像的标签。
  3. 设置QEMU模拟器:安装并配置QEMU模拟器,使得Docker镜像可以跨平台构建。
  4. 设置Docker Buildx:安装并配置Docker Buildx插件,用于扩展Docker原生的构建功能。
  5. 登录到Docker Hub:使用环境变量中的Docker Hub用户名和GitHub仓库中的Docker Hub令牌(密钥)登录到Docker Hub。
  6. 构建并推送后端Docker镜像:
  • 使用backend目录下的Dockerfile构建名为"backend"的Docker镜像。
  • 为构建的镜像设置标签,格式为用户名/backend:sha_short
  • 将构建好的Docker镜像推送到Docker Hub。
  1. 构建并推送前端Docker镜像:
  • 使用frontend目录下的Dockerfile构建名为"frontend"的Docker镜像。
  • 为构建的镜像设置标签,格式为用户名/frontend:sha_short
  • 将构建好的Docker镜像推送到Docker Hub。

总之,当有新的提交推送到main分支时,这个GitHub Actions工作流会自动构建并推送名为"backend"和"frontend"的Docker镜像到Docker Hub,并使用当前提交的短SHA值(sha_short)作为镜像标签。

创建 Docker Hub Access Token

要在Docker Hub上创建一个访问令牌(Access Token),请按照以下步骤操作:

  1. 登录到你的Docker Hub账户。
  2. 点击右上角的头像,然后从下拉菜单中选择Account Settings(帐户设置)。
  3. 在左侧的导航菜单中,点击Security(安全)。
  4. 点击New Access Token(新访问令牌)按钮。
  5. 输入一个描述性的令牌名称,例如“GitHub Actions”,然后点击Create(创建)按钮。
  6. 成功创建令牌后,一个弹出窗口会显示新生成的访问令牌。请务必立即复制此令牌,因为你以后将无法再次查看它。如果你不小心关闭了窗口,你需要生成一个新的访问令牌。

现在你已经创建了一个Docker Hub访问令牌,可以在API调用、命令行工具或GitHub Actions工作流程中使用它进行身份验证。为了安全起见,请不要在公共场所共享你的访问令牌。

接下来,你可以在GitHub仓库中创建一个Docker Hub Secret,用于存储你刚刚创建的访问令牌。这样,在GitHub Actions工作流程中,你可以通过${{ secrets.DOCKERHUB_TOKEN }}引用这个Secret。

创建 Github Docker Hub Secret

进入 kubernetes-example 仓库的 Settings 页面,点击左侧的“Secrets”,进入“Actions”菜单,然后点击右侧“New repository secret”创建新的 Secret。

在 Name 输入框中输入 DOCKERHUB_TOKEN,这样在 GitHub Action 的 Step 中,就可以通过 ${{ secrets.DOCKERHUB_TOKEN }} 表达式来获取它的值。在 Secret 输入框中输入刚才我们复制的 Docker Hub Token,点击“Add secret”创建。

要在GitHub仓库中创建一个Docker Hub Secret,你需要执行以下步骤:

  1. 登录到你的GitHub账户,然后转到要为其创建Secret的仓库。
  2. 点击仓库页面顶部的Settings(设置)选项卡。
  3. 在左侧的导航菜单中,点击Secrets and Variables
  4. 点击页面右上角的New repository secret(新建仓库密钥)按钮。
  5. 输入一个名称,例如DOCKERHUB_TOKEN,并输入你的Docker Hub访问令牌(也可以是密码)作为值。点击Add secret(添加密钥)按钮以保存。

现在,你已经成功创建了一个名为DOCKERHUB_TOKEN的Secret,它存储了你的Docker Hub访问令牌。在GitHub Actions工作流程中,你可以通过${{ secrets.DOCKERHUB_TOKEN }}引用这个Secret。

请注意,为了安全起见,GitHub不允许在工作流程日志中显示Secret的值,因此它们会自动被隐藏。此外,为了防止泄露,GitHub不允许在公共仓库的Forks上的工作流程中使用Secret。

触发 GitHub Action Workflow

git clone https://github.com/cloudzun/kubernetes-example
cd kubernetes-example

向仓库提交一个空 commit

git commit --allow-empty -m "Trigger Build"

使用 git push 来推送到仓库,这将触发工作流

git push origin main


后记

本课程体系旨在为学员提供一套全面的云原生技术学习路径,包括容器技术、Kubernetes 部署和管理、Ingress 管理、持久化存储、应用部署、可观测性、服务网格、DevOps 工具链和流水线以及 GitOps 等方面的内容。通过学习这套课程体系,学员将掌握云原生技术的关键概念、原理和实践方法,从而提升云原生应用的部署、管理和协作效率。

  1. Docker 技术实战:学习容器技术的基础知识,包括 Docker 的运作原理、核心组件以及应用部署和监控管理。
  2. Kubernetes 部署和管理实战:深入了解 Kubernetes 的核心概念和基础组件,掌握 Kubernetes 集群的部署、管理和监控技能。
  3. Kubernetes Ingress 管理与运维:学习 Kubernetes Ingress 的概念、工作机制和运维管理方法,提升 Ingress 的部署和管理能力。
  4. Kubernetes 持久化存储实践:探讨 Kubernetes 持久化存储的原理、实现方式和实际应用,提高在 Kubernetes 上实现持久化存储的能力。
  5. Kubernetes 应用部署和管理:学习 Kubernetes 应用部署和管理的基本概念、原理和实现方法,掌握在 Kubernetes 上部署和管理应用的技巧。
  6. Kubernetes 可观测性实践:研究 Kubernetes 可观测性实践的概念、原理和实现方法,涉及监控、日志记录和分析等方面。
  7. 基于 Istio 的服务网格实战:深入了解基于 Istio 的服务网格概念、原理和实现方法,包括服务路由、流量管理、弹性能力和微服务可观测性等。
  8. DevOps 工具链与流水线实践:探讨 DevOps 工具链与流水线的概念、原理和实现方法,涵盖自动化构建、测试、部署和监控等环节。
  9. GitOps 云原生应用的高效部署与管理:全面掌握 GitOps 原理、技术和工具,提高云原生应用部署、管理和协作效率。

通过对这九门课程的学习,学员可以:

  1. 熟练运用 Docker 和 Kubernetes 进行容器化应用的部署和管理。
  2. 掌握 Ingress 和持久化存储在 Kubernetes 集群中的配置和管理方法。
  3. 了解 Kubernetes 可观测性实践,提高应用监控、日志和分析的能力。
  4. 探究 Istio 服务网格的原理和实现,实现微服务的高可用、弹性和安全。
  5. 运用 DevOps 工具链和流水线实现自动化构建、测试、部署和监控。
  6. 掌握 GitOps 的原理和技术,提升云原生应用的部署、管理和协作效率。

本课程体系适合对云原生技术感兴趣的开发者、运维工程师、架构师等专业人士。课程内容丰富、实践性强,旨在帮助学员在实际工作中更好地应用云原生技术,为企业的数字化转型做出贡献。