环境 版本
本地环境 macOS Catalina
远程环境 CentOS Linux release 7.7.1908 (Core)
Docker Engine Community 19.03.2
Docker Compose Version 1.24.1

Docker 除了可以从官方镜像仓库下载和上传镜像,还可以使用 Dockerfile 自己编译镜像和使用私有或第三方镜像仓库。

Dockerfile

Dockerfile 文件中包含用于自动构建镜像的指令,构建过程中,每条指令都会生成一个只读层,每一层都是上一层的增量,最后堆叠起来,构成了镜像。

编译 Go 实现的 HTTP 服务:

git clone https://gist.github.com/d13f96506aebed11af6e6799f71acba4.git
cd d13f96506aebed11af6e6799f71acba4
vim Dockerfile

如果无法 Clone,可以复制 server.go 的代码:

package main

import (
	"fmt"
	"log"
	"net/http"
)

func Reverse(s string) string {
	r := []rune(s)
	for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 {
		r[i], r[j] = r[j], r[i]
	}
	return string(r)
}

func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "hi %s", Reverse(r.URL.Path[1:]))
}

func main() {
	http.HandleFunc("/", handler)
	log.Println("server run in 0.0.0.0:8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

然后新增 Dockerfile 文件,内容如下:

FROM golang:alpine as builder
ADD . /workspace
WORKDIR /workspace
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o web-server .

FROM scratch
COPY --from=builder /workspace/web-server /web-server
EXPOSE 8080
CMD ["/web-server"]

上面的 Dockerfile 使用了多阶段构建(Multi Stage),构建过程分成了 2 个阶段,第 1 阶段使用预装 Go 的 Alpine 镜像编译程序,第 2 阶段使用一个空镜像 scratch ,将第一阶段编译的二进制文件文件 web-server 复制到根目录,定义镜像开放端口并执行。

执行 docker build -t test-web-server . 编译镜像。编译成功后,可通过 docker images 指令查看镜像列表确认。

运行容器测试下结果:

docker run -d --name test-web-server001 -p 8080:8080 test-web-server
docker ps
docker logs test-web-server001
curl 127.0.0.1:8080/docker-and-compos

完整 Dockerfile 指令说明

# 指定基础镜像,并添加别名
FROM golang:alpine as builder

# 标识 image 作者
MAINTAINER zzir

# 将当前目录下文件添加到容器 /workspace
ADD . /workspace

# 指定当前工作目录
WORKDIR /workspace

# 执行命令,编译 Go 项目
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o web-server .


# 第二阶段构建 ---------- ---------- ---------- ---------- ----------
FROM alpine

# 添加标签
LABEL version="1.0" description="test"

# 定义构建参数,可在构建时通过 --build-arg 改变
ARG key=default_value

# 将上一阶段编译的文件拷贝到当前镜像
COPY --from=builder /workspace/web-server /usr/local/bin/

# 设置环境变量
ENV key value

# 执行命令,添加用户
RUN adduser -S -D -H -h /home/app app

# 指定用户
USER app

# 声明端口
EXPOSE 8080

# 定义挂载点
VOLUME ["/data"]

# ENTRYPOINT 指令存在时,CMD 都是 ENTRYPOINT 的参数
# ENTRYPOINT ["web-server"]

# 指定容器启动时执行的命令
CMD ["web-server"]

# 指定默认 SHELL
SHELL ["/bin/sh", "-c"]

# 其它镜像以当前镜像为基础镜像时触发 
ONBUILD ADD . /tmp/

# 指定退出的信号值
STOPSIGNAL SIGKILL

# 配置健康检查
HEALTHCHECK --interval=1m --timeout=10s --start-period=10s --retries=3  CMD curl -f http://127.0.0.1:8080/ || exit 1

由于 alpine 基础镜像默认没有安装 curl,docker ps 查看容器健康检测会显示 unhealthy。解决方法:

  1. 编译镜像时执行 RUN apk add curl -y
  2. 将健康检测 CMD 替换为 wget -q -O - 127.0.0.1:8080/asdf

ENTRYPOINT 和 CMD

ENTRYPOINT 和 CMD 构建阶段都会被忽略,而是在启动时执行。多数情况下它们应该是单独使用的,有一种例外是 CMD 为 ENTRYPOINT 提供默认可选参数,比如:

...
ENTRYPOINT ["ls", "/"]
CMD ["-a", "-l"]

这种情况下容器启动时执行的是 ls / -a -l 。但也可以启动时覆盖:

docker run --entrypoint /bin/cat <container> -n /etc/passwd

这样就把默认启动命令 ls / -a -l 替换成了 /bin/cat -n /etc/passwd。

关于 ENTRYPOINT 和 CMD 更具体可以参考官方文档:https://docs.docker.com/engine/reference/builder/#understand-how-cmd-and-entrypoint-interact

官方仓库操作

最常用的 PULL 应该很熟悉了,我们尝试把编译的镜像上传到官方镜像仓库。

我们还使用最开始的 Dockerfile:

FROM golang:alpine as builder
ADD . /workspace
WORKDIR /workspace
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o web-server .

FROM scratch
COPY --from=builder /workspace/web-server /web-server
EXPOSE 8080
CMD ["/web-server"]

并重新编译镜像:

docker build -t test-web-server .

登录:

docker login --username=zzir --password='********'

为镜像打上标签:

docker tag test-web-server zzir/test-web-server:latest

上传镜像:

docker push zzir/test-web-server:latest

默认是公开镜像(注意请勿上传敏感信息到公开镜像),至此你已经可以在其它地方下载 zzir/test-web-server:latest 啦,这里我使用的是自己的用户名,你需要更换成你自己的,或者重新注册一个。

下载验证:

docker pull zzir/test-web-server

也可以登录 https://cloud.docker.com/repository/docker/zzir/test-web-server/ 查看。

搭建私有仓库 Registry

上面我们尝试手动编译镜像并上传到官方公开镜像仓库,但工作中我们可能对镜像仓库需要一些更定制化的需求,官方也给出了私有仓库的搭建方法。

搭建私有仓库非常简单,使用 Docker 启动:

docker run -d -p 5000:5000 --restart=always --name registry -v /data/registry:/var/lib/registry registry

需要编辑 /etc/docker/daemon.json 配置 Docker 仓库地址:

192.168.5.242 是我的远程服务器地址,这里需要换成你的服务器地址。

{
    "insecure-registries":[
      "192.168.5.242:5000"
    ]
}

给镜像打上标签:

docker pull zzir/test-web-server
docker tag zzir/test-web-server:latest 192.168.5.242:5000/test-web-server:latest

推送到私有镜像仓库:

docker push 192.168.5.242:5000/test-web-server:latest

通过 API 确认下:

curl 192.168.5.242:5000/v2/_catalog

返回 {"repositories":[test-web-server"]} 说明镜像已经上传成功。

更多 API 可以查看 https://docs.docker.com/registry/spec/api/

删除本地镜像再下载测试:

docker rmi 192.168.5.242:5000/test-web-server
docker pull 192.168.5.242:5000/test-web-server

没有问题。

私有仓库 Registry WEB UI

如果你不想通过 API 查看私有镜像仓库状态,也可以安装个镜像仓库的 WEB UI。

直接通过 Docker 安装:

docker run -d --name registry-ui -p 5001:80  --link registry -e URL=http://192.168.5.242:5000 -e DELETE_IMAGES=true joxit/docker-registry-ui:static

浏览器访问 192.168.5.242:5001 发现 CORS 跨域错误,需要配置 registry 容器,为方便我们直接进入容器修改:

docker exec -ti registry /bin/sh

修改 /etc/docker/registry/config.yml :

headers:
    X-Content-Type-Options: [nosniff]
    Access-Control-Allow-Origin: ['*']
    Access-Control-Allow-Methods: ['*']
    Access-Control-Allow-Headers: ['*']
    Access-Control-Max-Age: [1728000]
    Access-Control-Allow-Credentials: [true]
    Access-Control-Expose-Headers: ['Docker-Content-Digest']

保存并重启 registry 容器,更优雅的做法是将配置文件挂载入 registry 容器。

完整 registry 配置:

version: 0.1
log:
  fields:
    service: registry
storage:
  cache:
    blobdescriptor: inmemory
  filesystem:
    rootdirectory: /var/lib/registry
http:
  addr: :5000
  headers:
    X-Content-Type-Options: [nosniff]
    Access-Control-Allow-Origin: ['*']
    Access-Control-Allow-Methods: ['*']
    Access-Control-Allow-Headers: ['*']
    Access-Control-Max-Age: [1728000]
    Access-Control-Allow-Credentials: [true]
    Access-Control-Expose-Headers: ['Docker-Content-Digest']
health:
  storagedriver:
    enabled: true
    interval: 10s
    threshold: 3

更多

  • https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
  • https://docs.docker.com/engine/reference/builder/
  • https://docs.docker.com/engine/reference/builder/#understand-how-cmd-and-entrypoint-interact
  • https://docs.docker.com/compose/
  • https://docs.docker.com/registry/
  • https://github.com/docker/distribution
  • https://docs.docker.com/registry/spec/api/