Eli's Blog

1. Dockerfile 指令

  • FROM: 指定基础镜像

    • 服务类镜像: nginxredismongomysqlhttpdphptomcat
    • 语言类镜像: nodeopenjdkpythonrubygolang
    • 操作系统镜像: ubuntudebiancentosfedoraalpine
    • 空白镜像:scratch 适用于静态编译的程序,不需要操作系统支撑。
  • COPY: 复制文件

  • ADD: 支持添加URL,自动解压文件等

  • WORKDIR: 指定默认目录工作

  • RUN: 构建镜像时执行, 用于安装应用和软件包,创建用户等操作

  • CMD: 运行容器的启动命令,可被替换

  • ENTRYPOINT: 同CMD, 但支持额外参数

  • ENV: 设置环境变量

  • VOLUME: 定义匿名卷

  • EXPOSE: 曝露端口

  • USER:指定当前用户

  • HEALTHCHECK

2. 使用scratch镜像

2.1 直接使用编译好的C程序 (依赖外部库,直接报错)

1
2
3
4
5
#include <stdio.h>

void main() {
printf("hello world\n");
}
1
2
3
4
FROM scratch

COPY hello /
CMD ["/hello"]
1
2
3
4
5
6
gcc hello.c -o hello

docker build -t hello .

docker run --rm hello
standard_init_linux.go:190: exec user process caused "no such file or directory"

2.2 改用golang程序

1
2
3
4
5
6
7
package main

import "fmt"

func main() {
fmt.Println("hello world")
}
1
2
3
4
5
6
7
8
FROM golang as builder
WORKDIR /go/src/app
COPY hello.go .
RUN go build -ldflags="-w -s" hello.go

FROM scratch
COPY --from=builder /go/src/app/hello /
CMD ["/hello"]
1
2
docker build -t hello .
docker run --rm hello # hello world

2.3 不使用标准库的C版本

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <sys/syscall.h>

#ifndef DOCKER_GREETING
#define DOCKER_GREETING "Hello from Docker"
#endif

const char message[] = DOCKER_GREETING "\n";

void _start() {
syscall(SYS_write, 1, message, sizeof(message)-1);

syscall(SYS_exit, 0);
}
1
2
3
4
FROM scratch

COPY hello /
CMD ["/hello"]
1
2
3
4
5
# 静态编译 yum install glibc-static
gcc -static -Os -nostartfiles -fno-asynchronous-unwind-tables -o hello hello.c

docker build -t hello .
docker run --rm hello # Hello from Docker

3. 错误的文件系统操作

在 Shell 中,连续两行是同一个进程执行环境,因此前一个命令修改的内存状态,会直接影响后一个命令;而在 Dockerfile 中,这两行 RUN 命令的执行环境根本不同,是两个完全不同的容器。这就是对 Dockerfile 构建分层存储的概念不了解所导致的错误。

1
2
RUN cd /app
RUN echo "hello" > world.txt # 文件并不在/app目录下

4. RUN & CMD & ENTRYPOINT

4.1 docker中的进程,必须以前台方式启动

对于容器而言,其启动程序就是容器应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,从而退出,其它辅助进程不是它需要关心的东西。

1
2
3
4
5
6
7
8
9
CMD echo $HOME
CMD ["sh", "-c", "echo $HOME"] # 实际执行命令

# 错误的示范
CMD service nginx start
CMD ["sh", "-c", "service nginx start"] # 实际执行命令

# 正确的nginx启动命令, 必须以前台形式运行
CMD ["nginx", "-g", "daemon off"]

4.2 支持额外参数

1
2
3
4
5
6
7
8
FROM ubuntu

RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lob/apt/lists/*

#CMD ["curl", "-s", "https://cip.cc"] # 不主持额外参数
ENTRYPOINT ["curl", "-s", "https://cip.cc"] # 支持额外参数,比如 curl -i
1
2
3
4
docker build -t myip .

docker run --rm myip
docker run --rm myip -i # -i, 获取HTTP请求头,但这里报错,无法将该参数传入

4.3 应用运行前的准备工作

某些应用,需要在运行主进程钱,做一些准备工作。mysql需要提前进行数据库配置、初始化工作

1
2
3
4
5
6
7
8
9
FROM alpine:3.4
...
RUN addgroup -S redis && adduser -S -G redis redis
...
ENTRYPOINT ["docker-entrypoint.sh"]

USER redis
EXPOSE 6379
CMD [ "redis-server" ]
1
2
3
4
5
6
7
8
9
#!/bin/sh
...
# allow the container to be started with `--user`
if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
chown -R redis .
exec su-exec redis "$0" "$@"
fi

exec "$@"
1
2
3
4
5
# id 命令将替换默认的 CMD ["redis-server"] 命令
docker run -it redis id

# 正确的启动方式
docker run --name redis-srv -d redis

5. 其他示例

5.1 命令细节说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
FROM busybox
MAINTAINER "eli.he@live.cn"
# LABEL maintainer="eli.he@live.cn"

ENV WEB_ROOT="/data/www/html/"
WORKDIR ${WEB_ROOT}

COPY index.html .
COPY app ./app # 拷贝目录时,目录不会自动创建,需要指定

ADD http://nginx.org/download/nginx-1.19.2.tar.gz /usr/local/src/ # 只下载,不解压
ADD nginx-1.19.2.tar.gz /usr/local/src/ # 自动解压

VOLUME /data/www/mysql

EXPOSE 80/tcp 443/tcp # 容器运行时,-P 自动暴露

# RUN 打包镜像时运行
RUN cd /usr/local/src/ && tar xf nginx-1.19.2.tar.gz

# CMD 容器启动时运行,可被docker run中指定的命令替换
CMD /bin/httpd -f -h $WEB_ROOT # ok
CMD ["/bin/httpd", "-f", "-h $WEB_ROOT"] # 无法解析变量
CMD ["/bin/sh", "-c", "/bin/httpd", "-f", "-h $WEB_ROOT"] # 能解析,但执行完shell立即退出

# ENTRYPOINT 指定容器的默认运行程序,docker run中指定的命令,无法替换它,只能被当初参数传递给该默认程序
ENTRYPOINT /bin/httpd -f -h $WEB_ROOT

# 注意: CMD & ENTRYPOINT 同时存在时,CMD 中的数据被当成参数传递给 ENTRYPOINT
CMD ["/bin/httpd", "-f", "-h $WEB_ROOT"]
ENTRYPOINT ["/bin/sh", "-c"]

HEALTHCHECK --interval=5m --timeout=3s \
CMD curl -f http://localhost/ || exit 1

5.2 自定义nginx镜像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
FROM nginx:1.19.2-alpine
LABEL maintainer="eli.he@live.cn"

ENV WEB_ROOT="/data/www/html/"
WORKDIR $WEB_ROOT

ADD index.html ./
ADD entrypoint.sh /bin/

RUN chmod +x /bin/entrypoint.sh

EXPOSE 80/tcp

HEALTHCHECK --start-period=3s CMD curl -o - -q http://${IP:-0.0.0.0}:${PORT:-80}

CMD ["/usr/sbin/nginx", "-g", "daemon off;"]
ENTRYPOINT ["/bin/entrypoint.sh"]
1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/sh
# entrypoint.sh

cat > /etc/nginx/conf.d/http.conf <<EOF
server {
server_name $HOSTNAME;
listen ${IP:-0.0.0.0}:${PORT:-80};
root ${WEB_ROOT:-/usr/share/nginx/html};
}
EOF

exec "$@"
1
2
3
docker build -t myweb:v0.1 .

docker run --name web1 -P -d myweb:v0.1