一、Docker 简介
1、Docker 的英文翻译是“搬运工”的意思,他搬运的东⻄就是我们常说的集装箱 Container ,Container里面装的是任意类型的 App,我们的开发人员可以通过 Docker 将App 变成一种标准化的、可移植的、自管理的组件,我们可以在任何主流的操作系统中开发、调试和运行。
2、Docker Engine 是一个C/S架构的应用程序,主要包含下面几个组件:
二、镜像和容器的基本操作
1、获取镜像
Docker 官方提供了一个公共的镜像仓库:docker hub
docker pull ubuntu:16.04
2、运行(启动里面的 bash 并且进行交互式操作)
docker run -it --rm \
ubuntu:16.04 \
/bin/bash
-
docker run 就是运行容器的命令。
-
-it:这是两个参数,一个是 -i:交互式操作,一个是 -t 终端。我们这里打算进入 bash 执行一些命令并查看返回结果,因此我们需要交互式终端。
-
–rm:这个参数是说容器退出后随之将其删除。默认情况下,为了排障需求,退出的容器并不会立即删除,除非手动 docker rm。我们这里只是随便执行个命令,看看结果,不需要排障和保留结果,因此使用 --rm 可以避免浪费空间。
-
ubuntu:16.04:这是指用 ubuntu:16.04 镜像为基础来启动容器。
-
bash:放在镜像名后的是命令,这里我们希望有个交互式 Shell,因此用的是 bash。
### 通过 exit 退出了这个容器。
3、列出镜像
docker image ls
列表包含了仓库名、标签、镜像 ID、创建时间以及所占用的空间。镜像 ID 则是镜像的唯一标识,一个镜像可以对应多个标签。
4、镜像大小
查看镜像、容器、数据卷所占用的空间。
docker system df
5、新建并启动
docker run -t -i ubuntu:16.04 /bin/bash
其中, -t 选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上, -i 则让容器的标准输入保持打开。
当利用 docker run 来创建容器时,Docker 在后台运行的标准操作包括:
-
检查本地是否存在指定的镜像,不存在就从公有仓库下载
-
利用镜像创建并启动一个容器
-
分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
-
从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
-
从地址池配置一个 ip 地址给容器
-
执行用户指定的应用程序
-
执行完毕后容器被终止
6、启动已终止容器
可以利用 docker container start 命令,直接将一个已经终止的容器启动运行。
容器的核心为所执行的应用程序,所需要的资源都是应用程序运行所必需的。除此之外,并没有其它的资源。可以在伪终端中利用 ps 或 top 来查看进程信息。
可见,容器中仅运行了指定的 bash 应用。这种特点使得 Docker 对资源的利用率极高,是货真价实的轻量级虚拟化。
7、后台运行
需要让 Docker 在后台运行而不是直接把执行命令的结果输出在当前宿主机下。此时,可以通过添加 -d 参数来实现。下面举两个例子来说明一下。
如果不使用 -d 参数运行容器。容器会把输出的结果 (STDOUT) 打印到宿主机上面。
docker run ubuntu:16.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
如果使用了 -d 参数运行容器。此时容器会在后台运行并不会把输出的结果 (STDOUT) 打印到宿主机上面(输出结果可以用 dockerlogs 查看)。
docker run -d ubuntu:16.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
注: 容器是否会⻓久运行,是和 docker run 指定的命令有关,和 -d 参数无关。
使用 -d 参数启动后会返回一个唯一的 id,也可以通过 docker container ls 命令来查看容器信息。
docker container ls
docker container logs [container ID or NAMES]
8、终止容器
可以使用 docker container stop 来终止一个运行中的容器。此外,当 Docker 容器中指定的应用终结时,容器也自动终止。
只启动了一个终端的容器,用户通过 exit 命令或 Ctrl+d 来退出终端时,所创建的容器立刻终止。终止状态的容器可以用 docker container ls -a 命令看到。
处于终止状态的容器,可以通过 docker container start 命令来重新启动。
9、进入容器
在使用 -d 参数时,容器启动后会进入后台。某些时候需要进入容器进行操作:exec 命令 -i -t 参数。
只用 -i 参数时,由于没有分配伪终端,界面没有我们熟悉的 Linux 命令提示符,但命令执行结果仍然可以返回。 当 -i -t 参数一起使用时,则可以看到我们熟悉的 Linux 命令提示符。
docker run -dit ubuntu:16.04
docker container ls
docker exec -it 69d1 bash
9、删除容器
可以使用 docker container rm 来删除一个处于终止状态的容器。也可用使用 docker rm 容器名来删除,如果要删除一个运行中的容器,可以添加 -f 参数。Docker 会发送 SIGKILL 信号给容器。
用 docker container ls -a (或者docker ps -a) 命令可以查看所有已经创建的包括终止状态的容器
如果数量太多要一个个删除可能会很麻烦,用下面的命令可以清理掉所有处于终止状态的容器:
docker container prune
或者
docker ps -aq
10、删除本地镜像
如果要删除本地的镜像,可以使用`docker image rm·命令,其格式为:
docker image rm [选项] <镜像1> [<镜像2> ...]
或者
docker rmi 镜像名
或者用 ID、镜像名、摘要删除镜像 其中,<镜像> 可以是 镜像短 ID、镜像长 ID、镜像名 或者 镜像摘要。
docker image ls ```
#### docker image ls 默认列出的就已经是短 ID了,一般取前3个字符以上,只要足够区分于别的镜像就可以了。
#### 11、docker commit定制镜像
#### 镜像是容器的基础,每次执行 docker run 的时候都会指定哪个镜像作为容器运行的基础。
#### 镜像是多层存储,每一层是在前一层的基础上进行的修改;而容器同样也是多层存储,是在以镜像为基础层,在其基础上加一层作为容器运行时的存储层。
#### 以定制一个 Web 服务器为例子,来讲解镜像是如何构建的。
```bash
docker run --name webserver -d -p 80:80 nginx
这条命令会用 nginx 镜像启动一个容器,命名为 webserver,并且映射了 80 端口,这样我们可以用浏览器去访问这个 nginx 服务器。直接用浏览器访问的话,我们会看到默认的 Nginx 欢迎页面。
假设我们非常不喜欢这个欢迎页面,我们希望改成欢迎 Docker 的文字,我们可以使用 docker exec命令进入容器,修改其内容。
$ docker exec -it webserver bash
root@434ac74490a1:/# echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
root@434ac74490a1:/# exit
我们以交互式终端方式进入 webserver 容器,并执行了 bash 命令,也就是获得一个可操作的 Shell。然后,我们用 Hello, Docker!覆盖了 /usr/share/nginx/html/index.html的内容。 现在我们再刷新浏览器的话,会发现内容被改变了。
我们修改了容器的文件,也就是改动了容器的存储层。我们可以通过 docker diff 命令看到具体的改动。
现在我们定制好了变化,我们希望能将其保存下来形成镜像。
要知道,当我们运行一个容器的时候(如果不使用卷的话),我们做的任何文件修改都会被记录于容器存储层里。而 Docker 提供了一个 docker commit 命令,可以将容器的存储层保存下来成为镜像。换句话说,就是在原有镜像的基础上,再叠加上容器的存储层,并构成新的镜像。以后我们运行这个新镜像的时候,就会拥有原有容器最后的文件变化。
我们可以用下面的命令将容器保存为镜像:
docker commit \
--author "theRunCom" \
--message "修改了默认首页" \
webserver \
nginx:v2
在 docker image ls 中看到这个新定制的镜像
我们还可以用 docker history 具体查看镜像内的历史记录,如果比较 nginx:latest 的历史记录,我们会发现新增了我们刚刚提交的这一层。
新的镜像定制好后,我们可以来运行这个镜像。
12、Dockerfile 定制镜像
镜像的定制实际上就是定制每一层所添加的配置、文件等信息,但是命令毕竟只是命令,每次定制都得去重复执行这个命令,而且还不够直观,如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么这些问题不就都可以解决了吗?对的,这个脚本就是我们说的 Dockerfile 。
Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
定制 nginx 镜像为例:
mkdir mynginx
cd mynginx
touch Dockerfile
FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
FROM 就是指定基础镜像,因此一个 Dockerfile中 FROM 是必备的指令,并且必须是第一条指令。除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为 scratch 。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。对于Linux 下静态编译的程序来说,并不需要有操作系统提供运行时支持,所需的一切库都已经在可执行文件里了,因此直接 FROM scratch 会让镜像体积更加小巧。使用 Go 语言 开发的应用很多会使用这种方式来制作镜像,这也是为什么有人认为 Go 是特别适合容器微服务架构的语言的原因之一。
RUN 指令是用来执行命令行命令的。其格式有两种:
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
-
exec 格式:RUN [“可执行文件”, “参数1”, “参数2”],这更像是函数调用中的格式。 既然 RUN 就像Shell 脚本一样可以执行命令,那么我们是否就可以像 Shell 脚本一样把每个命令对应一个 RUN呢?比如这样:
FROM debian:jessie
RUN apt-get update
RUN apt-get install -y gcc libc6-dev make
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz"
RUN mkdir -p /usr/src/redis
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
RUN make -C /usr/src/redis
RUN make -C /usr/src/redis install
Dockerfile 中每一个指令都会建立一层,RUN 也不例外。上面的 Dockerfile 正确的写法应该是这样:
FROM debian:jessie
RUN buildDeps='gcc libc6-dev make' \
&& apt-get update \
&& apt-get install -y $buildDeps \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz" \
&& mkdir -p /usr/src/redis \
&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
&& make -C /usr/src/redis \
&& make -C /usr/src/redis install \&&
&&
&&
&&
rm -rf /var/lib/apt/lists/* \
rm redis.tar.gz \
rm -r /usr/src/redis \
apt-get purge -y --auto-remove $buildDeps
初学 Docker 制作出了很臃肿的镜像的原因之一,就是忘记了每一层构建的最后一定要清理掉无关文件。
13、构建镜像
在 Dockerfile 文件所在目录执行:
$ docker build -t nginx:v3 . 1 ⨯
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM nginx
---> 08b152afcfae
Step 2/2 : Run echo '<h1>Hello,Docker!</h1>' > /usr/share/nginx/html/index.html
---> Running in 824eec89916f
Removing intermediate container 824eec89916f
---> 39009e56a988
Successfully built 39009e56a988
Successfully tagged nginx:v3
$ docker run --name webserv3 -d -p 81:80 nginx:v3
14、镜像构建上下文
docker build 命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker 引擎中构建的。那么在这种客户端/服务端的架构中,如何才能让服务端获得本地文件呢?这就引入了上下文的概念。当构建的时候,用户会指定构建镜像上下文的路径,docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。
一般来说,应该会将 Dockerfile 置于一个空目录下,或者项目根目录下。如果该目录下没有所需文件,那么应该把所需文件复制一份过来。如果目录下有些东⻄确实不希望构建时传给 Docker 引擎,那么可以用 .gitignore 一样的语法写一个 .dockerignore ,该文件是用于剔除不需要作为上下文传递给Docker 引擎的。
一般大家习惯性的会使用默认的文件名 Dockerfile,以及会将其置于镜像构建上下文目录中。
15、迁移镜像
docker load 和 docker save 命令,用以将镜像保存为一个 tar 文件,然后传输到另一个位置上,再加载进来。
保存镜像的命令为:
docker save alpine | gzip > alpine-latest.tar.gz
然后我们将 alpine-latest.tar.gz 文件复制到了到了另一个机器上,可以用下面这个命令加载镜像:
docker load -i alpine-latest.tar.gz
三、私有镜像仓库
拉取镜像:你可以通过 docker search 命令来查找官方仓库中的镜像,并利用 docker pull 命令来将它下载到本地。
登录:通过执行 docker login 命令交互式的输入用户名及密码来完成在命令行界面登录 Docker Hub。
注销:你可以通过 docker logout 退出登录。
docker search centos
推送镜像:用户也可以在登录后通过 docker push 命令来将自己的镜像推送到 Docker Hub。以下命令中的username 请替换为你的 Docker 账号用户名。
docker tag ubuntu:17.10 username/ubuntu:17.10
私有仓库:docker-registry 是官方提供的工具,可以用于构建私有的镜像仓库。
你可以通过获取官方 registry 镜像来运行。
docker run -d -p 5000:5000 --restart=always --name registry registry
这将使用官方的 registry 镜像来启动私有仓库。默认情况下,仓库会被创建在容器的 /var/lib/registry 目录下。你可以通过 -v 参数来将镜像文件存放在本地的指定路径。
docker run -d \
-p 5000:5000 \
-v /opt/data/registry:/var/lib/registry \
registry
在私有仓库上传、搜索、下载镜像:创建好私有仓库之后,就可以使用 docker tag 来标记一个镜像,然后推送它到仓库。例如私有仓库地址为 127.0.0.1:5000。使用 docker tag 将 ubuntu:latest 这个镜像标记为 127.0.0.1:5000/ubuntu:latest。 格式为 docker tag IMAGE[:TAG] [REGISTRY_HOST[:REGISTRY_PORT]/]REPOSITORY[:TAG]
docker tag ubuntu:latest 127.0.0.1:5000/ubuntu:latest
使用 docker push 上传标记的镜像。
docker push 127.0.0.1:5000/ubuntu:latest
用 curl 查看仓库中的镜像:
curl 127.0.0.1:5000/v2/_catalog
四、数据共享与持久化
在容器中管理数据主要有两种方式:
数据卷 是一个可供一个或多个容器使用的特殊目录,它绕过 UFS ,可以提供很多有用的特性:
注意:数据卷 的使用,类似于 Linux 下对目录或文件进行 mount,镜像中的被指定为挂载点的目录中的文件会隐藏掉,能显示看的是挂载的 数据卷。
1. 创建一个数据卷:
docker volume create my-vol
2. 查看所有的 数据卷:
docker volume ls
3. 主机里使用一下命令可以查看指定数据卷的信息
docker volume inspect my-vol
4. 启动一个挂载数据卷的容器:在用 docker run 命令的时候,使用–mount标记来将数据卷挂载到容器里。在一次docker run中可以挂载多个数据卷。下面创建一个名为 web 的容器,并加载一个数据卷到容器的/webapp 目录。
docker run -d -P \
--name web \
# -v my-vol:/wepapp \
--mount source=my-vol,target=/webapp \
training/webapp \
python app.py
5. 查看数据卷的具体信息:在主机里使用以下命令可以查看 web 容器的信息
docker inspect web
6. 删除卷
docker volume rm my-vol
7. 数据卷 是被设计用来持久化数据的,它的生命周期独立于容器,Docker 不会在容器被删除后自动删除数据卷,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的 数据卷。如果需要在删除容器的同时移除数据卷。可以在删除容器的时候使用 docker rm -v 这个命令。 无主的数据卷可能会占据很多空间,要清理请使用以下命令
docker volume prune
挂载主机目录
1. 挂载一个主机目录作为数据卷:使用 --mount 标记可以指定挂载一个本地主机的目录到容器中去。
docker run -d -P \
--name web \
# -v /src/webapp:/opt/webapp \
--mount type=bind,source=/src/webapp,target=/opt/webapp \
training/webapp \
python app.py
2. 上面的命令加载主机的 /src/webapp 目录到容器的 /opt/webapp目录。这个功能在进行测试的时候十分方便,比如用户可以放置一些程序到本地目录中,来查看容器是否正常工作。本地目录的路径必须是绝对路径,以前使用 -v 参数时如果本地目录不存在 Docker 会自动为你创建一个文件夹,现在使用 --mount 参数时如果本地目录不存在,Docker 会报错。(Docker 挂载主机目录的默认权限是 读写,用户也可以通过增加 readonly 指定为 只读。)
docker run -d -P \
--name web \
# -v /src/webapp:/opt/webapp:ro \
--mount type=bind,source=/src/webapp,target=/opt/webapp,readonly \
training/webapp \
python app.py
加了 readonly 之后,就挂载为 只读 了。如果你在容器内 /opt/webapp 目录新建文件,会显示如下错误:
/opt/webapp # touch new.txt
touch: new.txt: Read-only file system
查看数据卷的具体信息:在主机里使用以下命令可以查看 web 容器的信息
docker inspect web
挂载一个本地主机文件作为数据卷: --mount 标记也可以从主机挂载单个文件到容器中
$ docker run --rm -it \
# -v $HOME/.bash_history:/root/.bash_history \
--mount type=bind,source=$HOME/.bash_history,target=/root/.bash_history \
ubuntu:17.10 \
bash
root@2affd44b4667:/# history
1 ls
2 diskutil list