本章讲的大概能让你明白虚拟机和 docker 的区别...
docker 设置了两种构建镜像的方式:
通过 docker commit 构建镜像(不推荐)
这个命令是将先有的容器制作成镜像, 不过建议仅用在排查问题的时候使用, 平时生成容器时最好不要用这种镜像, 因为不知道里面有什么改动, 对于开发者来说完全是一个黑盒
命令格式:
docker commit [参数] <容器 ID 或 容器名> [仓库名[:标签]] [flags]
比如(我随便找了一个本地容器 ID):
➜ ~ docker commit -m "这是一个测试镜像" -a "GPF" 5ad06ec670eb local_nginx:v1
sha256:134e09cdce58842dea03202aa5b6516ead8268afe78d2203be8595b4f0bc5ebe
➜ ~ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
local_nginx v1 134e09cdce58 2 seconds ago 109MB
这就把当前的容器转换成了镜像, 提交到远程就是:
docker push local_nginx:v1
和git
很像对不对? 不过推送到远程时需要有个 dockerhub 的账号
docker history
命令可以查看镜像的构建记录
➜ ~ docker history 134e09cdce58
IMAGE CREATED CREATED BY SIZE COMMENT
134e09cdce58 12 seconds ago nginx -g daemon off; 0B 这是一个测试镜像
649dcb69b782 4 days ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon… 0B
<missing> 4 days ago /bin/sh -c #(nop) STOPSIGNAL [SIGTERM] 0B
<missing> 4 days ago /bin/sh -c #(nop) EXPOSE 80/tcp 0B
<missing> 4 days ago /bin/sh -c ln -sf /dev/stdout /var/log/nginx… 22B
<missing> 4 days ago /bin/sh -c set -x && apt-get update && apt… 53.7MB
<missing> 4 days ago /bin/sh -c #(nop) ENV NJS_VERSION=1.15.1.0.… 0B
<missing> 4 days ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.15.1-… 0B
<missing> 11 days ago /bin/sh -c #(nop) LABEL maintainer=NGINX Do… 0B
<missing> 11 days ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 11 days ago /bin/sh -c #(nop) ADD file:28fbc9fd012eef727… 55.3MB
通过 Dockerfile 构建镜像
一个 Dockerfile 就是一个构建镜像的脚本, 常用的几个命令也不多, 也就FROM
, COPY
, RUN
, ADD
, ARG
, ENV
, VOLUME
, EXPOSE
, CMD
, LABEL
, 其他的一些命令就不再这里说了, 想了解完整的 Dockerfile 的关键词看这个 Dockerfile reference
一个简单的 demo
接下来我们就用一个完整的 Dockerfile 来示范一下:
首先创建一个Dockerfile
mkdir dockerfile_demo && cd dockerfile_demo
vi Dockerfile
# dockerfile 内容
# 继承的 ubuntu 镜像的版本号
FROM ubuntu:16.04
# 将本地 workdir 的文件复制到镜像内部, 另外有个 ADD 指令和这个效果类似, 不过不推荐使用
COPY ./sources.list /etc/apt/sources.list
# 在镜像内部执行命令, 这里就是更新版本和安装 nginx
RUN apt-get update && apt-get install -y nginx
# 修改 nginx 默认的 index.html 的内容
RUN echo 'Hi, I am in your container'\
>/var/www/html/index.html
# 镜像对外暴露80端口
EXPOSE 80
然后为了更新速度快一点, 就要换一个国内镜像
vi sources.list
# sources.list 内容
deb http://mirrors.aliyun.com/ubuntu/ xenial main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-proposed main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-proposed main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse
然后还是在这个目录下, 运行:
docker build ./ -t local_ubuntu_nginx:v2
执行完毕后就能看到本地多了一个镜像
➜ Documents docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
local_ubuntu_nginx v2 b0f5984c042a 14 minutes ago 214MB
用这个镜像构建一个容器:
➜ Documents docker run -it --rm -p 8088:80 local_ubuntu_nginx:v2 /bin/bash
root@5cb9ff723bee:/# /usr/sbin/nginx
root@5cb9ff723bee:/#
保持终端打开, 本地访问 http://localhost:8088/
就能看到欢迎界面了
如果这个是关闭交互端口, 这个就访问不到了, 这里要说一个关键点了
在 docker 中运行的程序不能使用后台运行的模式, 否则 docker 会任务这个容器不活跃或出现问题而自动关闭
但是 docker 容器本身是可以后台运行的: docker run -d ......
关于 docker build 多说一句
之前我们构建时执行的:
docker build ./ -t local_ubuntu_nginx:v2
这个 ./
的路径指的是构建文本流(context)的路径, 而不是 Dockerfile
的文件路径, 在 Dockerfile
中用的各种相对路径都是基于 context
的,我们完全可以是
docker build /path/to/context -f /path/to/anywhere/Dockerfile_demo -t local_ubuntu_nginx
你看, Dockerfile 的文件名不就变成 Dockerfile_demo
了, 如果有
COPY ./sources.list /etc/apt/sources.list
这样的操作, 那么它的完整路径应该是 /path/to/context/sources.list
, 不过默认的情况下就是这两个路径是一起的, 不设置镜像tag
的话就拿 Dockerfile 所在的目录名为镜像名,默认latest
版本
多阶段构建
有时候我们的运行环境和编译环境是两回事, 就拿 golang 来举例, 我们只会去维护代码, 而不去管它生成的二进制包是什么, 又因为golang 打包出来的二进制文件几乎是没有依赖, 只要执行这个文件就行, 那么在运行环境中和编译相关的程序就是多余的, 可以看一下官网的示例 Use multi-stage builds, 不能翻墙的看这个
FROM golang:1.7.3
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
# 这个是关键, --from=0 是指从第一个镜像(from)中复制内容, 程序中的1 就是0
COPY --from=0 /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]
$ docker build -t alexellis2/href-counter:latest .
这是我自己构建镜像时编译 swoole.so
的镜像和运行时的镜像大小
我们只想要一个 swoole.so
像 gcc
, make
都是不是代码运行时需要的模块, 因此只是在编译的时候用上, 吐一个swoole.so
出来就行, 相关代码
FROM php:5.6.36-cli-alpine3.7 as builder
# 中国特色
RUN echo "http://mirrors.ustc.edu.cn/alpine/v3.7/main/" > /etc/apk/repositories
# 添加编译 swoole 需要的前置插件
RUN apk update && \
apk upgrade && \
apk add alpine-sdk linux-headers && \
apk add autoconf gcc make
RUN wget https://github.com/swoole/swoole-src/archive/1.8.12-stable.tar.gz && \
tar zxvf 1.8.12-stable.tar.gz && \
cd swoole-src-1.8.12-stable && \
phpize && \
./configure && \
make && make install
FROM php:5.6.36-cli-alpine3.7 as runtimer
# 这里因为给每一个阶段加了别名, 这样更方便一点
COPY --from=builder /usr/local/lib/php/extensions/no-debug-non-zts-20131226/swoole.so /usr/local/lib/php/extensions/no-debug-non-zts-20131226/swoole.so
COPY ./swoole.ini /usr/local/etc/php/conf.d/swoole.ini
# 通常 run 是构建镜像时用的, 会保存一层缓存, cmd 就是镜像启动后执行的命令
RUN ["php", "-m"]
CMD ["php", "-a"]
构建镜像时需要注意的几点
- 尽量使用官方镜像, 同时版本选择
alpine > debian > ubuntu > centos
,alpine
版本的镜像是最小的 - COPY 和 ADD 尽量使用 COPY, COPY 只是单纯的复制, ADD 则会自动执行一些东西, 有可能出现意料外的问题
- CMD 和 ENTERPOINT 这两个可以查一下区别, 我个人习惯用 CMD
- 容器中的程序都要前台执行的模式, 使用 daemon 模式会被退出(你想想本身一个后台运行的容器中有个后台运行的程序...)
- 每一个
RUN
指令就是一层缓存, 当其中一个步骤发生更改那么这之后的步骤也都重新构建 -
系统更新最好合成一个 RUN 比如:
RUN apk update && \ apk upgrade && \ apk add alpine-sdk linux-headers && \ apk add autoconf gcc make
同样还是为了避免因为缓存时出现问题
- 多看看官方构建的镜像, 收获会很多, 比如 docker-nginx, docker-php
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。