在 Docker 17.05 版本前,构建 Docker 镜像时,通常会采用两种方式:
全部放入一个 Dockerfile一种方式是将所有的构建过程编包含在一个 Dockerfile 中,包括项目及其依赖库的编译、测试、打包等流程,这里可能会带来的一些问题:
Dockerfile 特别长,可维护性降低镜像层次多,镜像体积较大,部署时间变长源代码存在泄露的风险例如:
编写 app.go 文件,输出 Hello World
package main import "fmt" func main(){ fmt.Printf("Hello World!"); }编写 Dockerfile.one 文件
FROM golang:1.9-alpine RUN apk --no-cache add git ca-certificates WORKDIR /go/src/github.com/go/helloworld/ COPY app.go . RUN go get -d -v github.com/go-sql-driver/mysql \ && CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . \ && cp /go/src/github.com/go/helloworld/app /root WORKDIR /root/ CMD ["./app"]构建镜像
$ docker build -t go/helloworld:1 -f Dockerfile.one . 分散到多个Dockerfile另一种方式,就是我们事先在一个 Dockerfile 将项目及其依赖库编译测试打包好后,再将其拷贝到运行环境中,这种方式需要我们编写两个 Dockerfile 和一些编译脚本才能将其两个阶段自动整合起来,这种方式虽然可以很好地规避第一种方式存在的风险,但明显部署过程较复杂。
编写 Dockerfile.build 文件
FROM golang:1.9-alpine RUN apk --no-cache add git WORKDIR /go/src/github.com/go/helloworld COPY app.go . RUN go get -d -v github.com/go-sql-driver/mysql \ && CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .编写 Dockerfile.copy 文件
FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY app . CMD ["./app"]新建 build.sh
#!/bin/sh echo Building go/helloworld:build docker build -t go/helloworld:build . -f Dockerfile.build docker create --name extract go/helloworld:build docker cp extract:/go/src/github.com/go/helloworld/app ./app docker rm -f extract echo Building go/helloworld:2 docker build --no-cache -t go/helloworld:2 . -f Dockerfile.copy rm ./app现在运行脚本即可构建镜像
$ chmod +x build.sh $ ./build.sh对比 两种方式生成的镜像大小
$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE go/helloworld 2 f7cf3465432c 22 seconds ago 6.47MB go/helloworld 1 f55d3e16affc 2 minutes ago 295MB 使用多阶段构建Docker v17.05 开始支持多阶段构建 ( multistage builds )。使用多阶段构建我们就可以很容易解决前面提到的问题,并且只需要编写一个Dockerfile :
FROM golang:1.9-alpine as builder RUN apk --no-cache add git WORKDIR /go/src/github.com/go/helloworld/ RUN go get -d -v github.com/go-sql-driver/mysql COPY app.go . RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o a pp . FROM alpine:latest as prod RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --from=0 /go/src/github.com/go/helloworld/app . CMD ["./app"]构建镜像
$ docker build -t go/helloworld:3 . 只构建某一阶段的镜像我们可以使用 as 来为某一阶段命名,例如
FROM golang:1.9-alpine as builder例如当我们只想构建 builder 阶段的镜像时,我们可以在使用 docker build命令时加上 --target 参数即可
$ docker build --target builder -t username/imagename:tag . 构建时从其它镜像复制文件上面使用 COPY --from=0/go/src/github.com/go/helloworld/app . 从上一阶段的镜像中复制文件,我们也可以复制任意镜像中的文件
$ COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf 其它制作镜像的方式从 rootfs 压缩包导入
格式: docker import [选项]<文件> |URL| - [<仓库名>[:<标签>]]
压缩包可以是本地文件、远程 Web 文件,甚至是从标准输入中得到。压缩包将会 在镜像 / 目录展开,并直接作为镜像第一层提交。
创建一个 OpenVZ 的 Ubuntu 14.04 模板的镜像:
$ docker import \ http://download.openvz.org/template/precreated/ubuntu-14.04- x86_64-minimal.tar.gz \ openvz/ubuntu:14.04 Downloading from http://download.openvz.org/template/precreated/ ubuntu-14.04-x86_64-minimal.tar.gz sha256:f477a6e18e989839d25223f301ef738b69621c4877600ae6467c4e528 9822a79B/78.42 MB docker save 和 docker loadDocker 还提供了 docker save 和 docker load 命令,用以将镜像保存为一个文件,然后传输到另一个位置上,再加载进来。这是在没有 Docker Registry 时的做法,现在已经不推荐,镜像迁移应该直接使用 Docker Registry,无论是直接使用 Docker Hub 还是使用内网私有 Registry 都可以。
保存镜像
$ docker save alpine -o filename $ file filename filename: POSIX tar archive这里的 filename 可以为任意名称甚至任意后缀名,但文件的本质都是归档文件
若使用 gzip压缩
$ docker save alpine | gzip > alpine-latest.tar.gz然后我们将 alpine-latest.tar.gz 文件复制到了到了另一个机器上,可以用下面这个命令加载镜像:
$ docker load -i alpine-latest.tar.gz Loaded image: alpine:latest如果我们结合这两个命令以及 ssh 甚至 pv 的话,利用 Linux 强大的管道,我们可以写一个命令完成从一个机器将镜像迁移到另一个机器,并且带进度条的功能:
docker save <镜像名> | bzip2 | pv | ssh <用户名>@<主机名> 'cat | docker load'每个镜像都由很多层次构成,Docker 使用 Union FS 将这些不同的层结合到一个镜 像中去。 通常 Union FS 有两个用途, 一方面可以实现不借助 LVM、RAID 将多个 disk 挂到同一个目录下,另一个更常用的就是将一个只读的分支和一个可写的分支联合在一起,Live CD 正是基于此方法可以允许在镜像不变的基础上允许用户在其上进行一 些写操作
容器是一个独立运行的一个或一组应用,以及它们的运行态环境。
容器的启动方式有两种,一种是基于镜像新建一个容器并启动,另外一种是将在终止状态的容器重新启动
命令为:sudo docker run
下面命令会输出一个 ‘hello world’
master@ML:~$ sudo docker run ubuntu /bin/echo 'hello world' hello world下面的命令会启动一个 bash 终端,允许用户交互
master@ML:~$ sudo docker run -it ubuntu /bin/bash root@70d670234f22:/# root@70d670234f22:/# echo 'hello world' hello world其中, -t 选项是让 Docker 分配一个伪终端并绑定到容器的标准输入上,-i 则让容器的标准输入保持打开
root@70d670234f22:/# pwd / root@70d670234f22:/# ls bin dev home lib64 mnt proc run srv tmp var boot etc lib media opt root sbin sys usr当利用 docker run 创建容器时, Docker 在后台运行的标准操作包括:
检查本地是否存在指定镜像,不存在从共有仓库下载利用镜像创建并启动一个容器分配一个文件系统,并在只读的镜像层外挂载一层可读写层从宿主住进配置的网桥接口中桥接一个虚拟接口到容器中从地址池配置一个 ip 地址给容器执行用户指定的应用程序执行完毕后容器被终止可以利用 docker container start 命令,直接将一个已经终止的容器启动运行
容器的核心为所执行的应用程序,所需要的资源都是应用程序运行所必须的。可以使用 ps 或 top 来查看进程信息
root@b4f0165cb922:/# ps PID TTY TIME CMD 1 pts/0 00:00:00 bash 10 pts/0 00:00:00 ps容器中仅运行了指定的 bash 应用,这使得 Docker 对资源的利用率极高,是货真价实的轻量级虚拟化
需要让 Docker 在后台运行而不是直接把执行命令的结果输出在当前宿主机下。可以通过 -d 参数实现
# 不适用 -d 容器会把输出的结果打印到宿主机上 master@ML:~$ sudo docker run ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done" hello world hello world # 使用 -d 参数,容器启动后会返回唯一id master@ML:~$ sudo docker run -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done" 598608f5521438bd1a54518913f85c04690d7e41efc9e837fa0bedd952b7b3c8 # 查看容器的输出信息 可以通过 docker container [container ID or NAMES] master@ML:~$ sudo docker logs 5986 hello world hello world hello world使用 docker container stop 来终止运行中的容器
当 Docker 容器中指定的应用终结时,容器也自动终止
启动容器后,可以通过 exit 命令或 ctrl + d 来退出终端,所创建的终端立即终止
终止状态的容器可以使用docker container ls -a 命令查看
docker container restart 命令会将一个运行中的容器终止,然后重新启动
docker attach 是 Docker 自带的命令。
master@ML:~$ sudo docker run -idt ubuntu [sudo] password for master: adcbd57153b62bd32f7a753a733a05aea049f05d06547ab29dd0e9471273ac28 master@ML:~$ sudo docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESadcbd57153b6 ubuntu "/bin/bash" 50 seconds ago Up 48 seconds youthful_beaver master@ML:~$ sudo docker attach adcbd root@adcbd57153b6:/#注意:如果从这个 stdin 中 exit,会导致容器停止
-i -t 参数
docker exec 后可以跟好多个参数,主要 -t -i 参数;当 -i -t 一起使用时,可以看到Linux命令提示符
master@ML:~$ sudo docker run -idt ubuntu 68487a5fd121121014e3f4332a318bf25344fa3cbe8ef7bbd2153ac0d203d4a4 master@ML:~$ sudo docker exec -it 6848 bash root@68487a5fd121:/#如果从这个 stdin 中exit,不会导致容器的停止。
如果要导出某个容器 可以使用 docker export 命令
master@ML:~$ sudo docker export 6848 > ubuntu.tar可以使用 docker import 从容器快照中再导入为镜像
$ cat ubuntu.tar | docker import - test/ubuntuV:1.0此外可以使用指定的 URL 或者某个目录来导入
$ docker import http://example.com/exampleimage.tgz example/imagerepo注:用户既可以使用 docker load 来导入镜像存储文件到本地镜像库,也可以使用 docker import 来导入一个容器快照到本地镜像库。这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。
使用 docker container rm 来删除一个处于终止状态的容器
$ docker container rm trusting_newton trusting_newton如果要删除运行中的容器,可以添加 -f 参数。
用 docker container ls -a 命令可以查看所有已经创建的包括终止状态的容器,使用下面命令可以清理所有处于终止状态的容器
master@ML:/usr/local/d$ sudo docker container prune WARNING! This will remove all stopped containers. Are you sure you want to continue? [y/N] y