一:基本概念。
docker镜像:分层存储。
docker容器:image和container的关系。
docker仓库:一种是公用官网仓库(https://hub.docker.com/),一种是私有仓库。
二:安装docker。
1.卸载旧版本:
yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-selinux \
docker-engine-selinux \
docker-engine
2.wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo ---采用阿里的镜像
3.安装依赖:
yum install -y yum-utils device-mapper-persistent-data lvm2
4.yum install docker-ce-18.06.1.ce-3.el7 -y 可以指定版本进行安装
docker info 命令查看版本详细信息,包含服务端和客户端。
5.镜像加速器:
编辑 /etc/default/docker 文件,在其中的
DOCKER_OPTS 中配置加速器地址:
DOCKER_OPTS="--registry-mirror=https://registry.docker-cn.com" 镜像地址采用国内的为好。
重新启动服务。
$ sudo service docker restart
三:使用镜像。
docker system df 查看镜像的所占用资源。
docker image prune 删除虚悬镜像,名为none的
docker pull 下载镜像。(docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签])
docker tag 修改标签
docker push 推送镜像
docker run it 镜像运行的id /bin/bash 运行镜像,并进入镜像内部查看。exit可推出。
docker image ls (docker images) 查看下载下来的镜像。列表包含了 仓库名 、 标签 、 镜像 ID 、 创建时间 以及 所占用的空间 。
虚悬镜像:特殊的镜像,这个镜像既没有仓库名,也没有标签,均为
docker image ls -f since=nginx:v2 -f 过滤。查看某个位置之后的镜像。
docker image ls -f before=nginx:v2 -f 过滤。查看某个位置之前的镜像。
docker image ls --format "{{.ID}}: {{.Repository}}" 只包含镜像ID和仓库名
docker image rm [选项] <镜像1> [<镜像2> ...] 删除本地镜像
3.1:利用commit理解镜像的构成。
docker exec -it webserver bash 进入镜像。
docker diff webserver 比对镜像容器的存储层修改了哪些。
docker history nginx:v2 具体查看镜像内的历史记录
(docker commit 意味着所有对镜像的操作都是黑箱操作,生成的镜像也被称为黑箱镜像,换句话说,就是除了制作镜像的人道执行过什么命令、怎么生成的镜像,别人根本无从得知。而且,即使是这个制作镜像的人,过一段时间
后也无法记清具体在操作的。虽然 docker diff 或许可以告诉得到一些线索,但是远远不到可以确保生成一致镜像的地步。这种黑箱镜像的维护工作是非常痛苦的。)
3.2。Dockerfile定制镜像。
from 指定基础镜像
run 执行命令,例如:
RUN 指令是用来执行命令行命令的。由于命令行的强大能力, RUN 指令在定制
镜像时是最常用的指令之一。其格式有两种:
shell 格式: RUN <命令> ,就像直接在命令行中输入的命令一样。刚才写的
Dockerfile 中的 RUN 指令就是这种格式。
RUN echo '
COPY 复制文件
格式:
COPY [--chown=
COPY [--chown=
COPY package.json /usr/src/app/ 例如
在使用该指令的时候还可以加上 --chown=
属用户及所属组。
COPY --chown=55:mygroup files* /mydir/
ADD 更高级的复制文件
在需要自动解压缩的场合使用 ADD:ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /
CMD 容器启动命令:
shell 格式: CMD <命令>
exec 格式: CMD ["可执行文件", "参数1", "参数2"...]
参数列表格式: CMD ["参数1", "参数2"...] 。在指定了 ENTRYPOINT 指
令后,用 CMD 指定具体的参数。
正确的做法是直接执行 nginx 可执行文件,并且要求以前台形式运行。比如:
CMD ["nginx", "-g", "daemon off;"]
ENTRYPOINT 入口点:
ENTRYPOINT 的格式和 RUN 指令格式一样,分为 exec 格式和 shell 格式。
场景一:让镜像变成像命令一样使用:
FROM ubuntu:18.04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
CMD [ "curl", "-s", "https://ip.cn" ]
场景二:应用运行前的准备工作:
FROM alpine:3.4
...
RUN addgroup -S redis && adduser -S -G redis redis
...
ENTRYPOINT ["docker-entrypoint.sh"]
EXPOSE 6379
CMD [ "redis-server" ]
可以看到其中为了 redis 服务创建了 redis 用户,并在最后指定了 ENTRYPOINT
为 docker-entrypoint.sh 脚本。
#!/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 "$@"
ENV 设置环境变量
格式有两种:
ENV
ENV
例如下:
ENV NODE_VERSION 7.2.0
RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \
&& curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \
&& gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \
&& grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c - \
&& tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 \
&& rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \
&& ln -s /usr/local/bin/node /usr/local/bin/nodejs
下列指令可以支持环境变量展开:
ADD 、 COPY 、 ENV 、 EXPOSE 、 LABEL 、 USER 、 WORKDIR 、 VOLUME 、
STOPSIGNAL 、 ONBUILD 。
ARG 构建参数:
格式: ARG <参数名>[=<默认值>] ARG 所设置的构建环境的环境变量。是不要因此就使用 ARG 保存密码之类的信息,因为docker history 还是可以看到所有值的。
VOLUME 定义匿名卷:
格式为:
VOLUME ["<路径1>", "<路径2>"...]
VOLUME <路径>
之前我们说过,容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中,后面的章节我们会进一步介绍 Docker 卷的概念。为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile 中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以常运行,不会向容器存储层写入大量数据。
VOLUME /data
这里的 /data 目录就会在运行时自动挂载为匿名卷,任何向 /data 中写入的信息都不会记录进容器存储层,从而保证了容器存层的无状态化。当然,运行时可以覆盖这个挂载设置。比如:
docker run -d -v mydata:/data xxxx
在这行命令中,就使用了 mydata 这个命名卷挂载到了 /data 这个位置,替代了 Dockerfile 中定义的匿名卷的挂载配置。
EXPOSE 声明端口:
格式为 EXPOSE <端口1> [<端口2>...]
EXPOSE 指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P时,会自动随机映射 EXPOSE 的端口。
要将 EXPOSE 和在运行时使用 -p <宿主端口>:<容器端口> 区分开来。 -p ,是映射宿主端口和容器端口,换句话说,就是将容器的对应端口服务公开给外界访问,而 EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。
WORKDIR 指定工作目录:
格式为 WORKDIR <工作目录路径>。
USER 指定当前用户
格式: USER <用户名>[:<用户组>]
USER 指令和 WORKDIR 相似,都是改变环境状态并影响以后的层。 WORKDIR是改变工作目录, USER 则是改变之后层的执行 RUN , CMD 以及ENTRYPOINT 这类命令的身份。
RUN groupadd -r redis && useradd -r -g redis redis
USER redis
RUN [ "redis-server" ]
希望以某个已经建立好的用户来运行某个服务进程,使用 gosu:
# 建立 redis 用户,并使用 gosu 换另一个用户执行命令
RUN groupadd -r redis && useradd -r -g redis redis
# 下载 gosu
RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.7/gosu-amd64" \
&& chmod +x /usr/local/bin/gosu \
&& gosu nobody true
# 设置 CMD,并以另外的用户执行
CMD [ "exec", "gosu", "redis", "redis-server" ]
USER 指定当前用户
HEALTHCHECK 健康检查:格式:
HEALTHCHECK [选项] CMD <命令> :设置检查容器健康状况的命令
HEALTHCHECK NONE :如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令
HEALTHCHECK 支持下列选项:
--interval=<间隔> :两次健康检查的间隔,默认为 30 秒;
--timeout=<时长> :健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒;
--retries=<次数> :当连续失败指定次数后,则将容器状态视为unhealthy ,默认 3 次。和 CMD , ENTRYPOINT 一样, HEALTHCHECK 只可以出现一次,如果写了多个,只有最后一个生效。
为了帮助排障,健康检查命令的输出(包括 stdout 以及 stderr )都会被存储于健康状态里,可以用 docker inspect 来查看。
ONBUILD 为他人做嫁衣裳: 格式: ONBUILD <其它指令> 。
FROM node:slim
RUN mkdir /app
WORKDIR /app
ONBUILD COPY ./package.json /app
ONBUILD RUN [ "npm", "install" ]
ONBUILD COPY . /app/
CMD [ "npm", "start" ]
这次我们回到原始的 Dockerfile ,但是这次将项目相关的指令加上ONBUILD ,这样在构建基础镜像的时候,这三行并不会被执行。然后各个项目的Dockerfile 就变成了简单地:
FROM my-node
是的,只有这么一行。当在各个项目目录中,用这个只有一行的 Dockerfile 构建镜像时,之前基础镜像的那三行 ONBUILD 就会开始执行,成功的将当前项目的代码复制进镜像、并且针对本项目执行 npm install ,生成应用镜像。
Dockerfie 官方文档:https://docs.docker.com/engine/reference/builder/
Dockerfile 最佳实践文档:https://docs.docker.com/develop/developimages/dockerfile_best-practices/
Docker 官方镜像 Dockerfile :https://github.com/docker-library/docs
多阶段构建:
在 Docker 17.05 版本之前,我们构建 Docker 镜像时,通常会采用两种方式:
全部放入一个 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
对比两种方式生成的镜像大小