Docker 数据管理
用户在使用 Docker 的过程中,往往需要能够查看容器内应用产生的数据,或者说需要把容器内的数据进行备份,再或者说多个容器之间需要进行数据的共享,那么这些就必然涉及到容器的数据管理操作。
在容器中管理数据主要有两种方式,第一种是通过数据卷来管理,第二种是通过数据件容器来管理
本章我们首先来介绍如何在容器内创建数据卷,并且把本地的目录或者文件挂载到容器中的数据卷中。接下来我会给大家介绍如何使用数据卷容器,在容器和主机容器和容器之间共享数据,并且实现数据的备份和恢复。
5.1 数据卷-1
首先我们来了解数据卷,数据卷是一个可供容器使用的特殊目录,它绕过文件系统可以提供很多有用的特性。 - 首先数据卷可以在容器之间共享和重用, - 对数据卷的修改会立马生效。 - 对数据卷的更新不会影响镜像, - 卷会一直存在,直到没有容器使用。
## 第一步, 查看帮助文档# sudo docker run --help-v, --volume list Bind mount a volume--volume-driver string Optional volume driver for the container--volumes-from list Mount volumes from the specified container(s)--name string Assign a name to the container
应用:
## 实际挂载一个镜像.# sudo docker run -d -P -ti --name volume_demo_1 -v /data_1 busybox
a41e30838125c0255161949f02cdb431cca93e47b639c1d215d3ed6312b06a6b ### 执行的反馈.### -P 参数 允许外部访问容器内需要暴露的端口.## 检查容器是否启动成功
# sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a41e30838125 busybox "sh" 13 minutes ago Up 13 minutes volume_demo_1## 检查数据卷是否挂载成功.
root@ubuntu-xenial:/home/vagrant# sudo docker attach a41e30(容器6 位缩写)
/ # ls
bin data_1 dev etc home proc root sys tmp usr var### 可以看到上面有 data_1这个数据卷, 说明挂载成功
那么这个挂载点它在我们本机上的目录映射到什么位置呢?我们可以通过 Docker 的 inspect 命令来查看一下。Inspect命令我在之前的课程给大家讲过,是用来查看这个容器相关维度的信息,那么它会返回比较详尽的关于容器的一些信息,我们现在关心的是挂载点的信息,所以我们来找一下
## 使用 Docker 的 inspect 命令查看挂载点映射的具体目录.# sudo docker inspect a41e
### 返回巨多内容, 因此下面使用精简命令.巨多命令见文末.
# sudo docker inspect a41e | grep Mounts -A 10 ### 打印匹配行以及匹配行后的 10 行内容"Mounts": [{"Type": "volume","Name": "102d3cd7ac8bb138751db24d4b8aabe4edc2a9870f6796e40e337f9f7a5e5139","Source": "/var/lib/docker/volumes/102d3cd7ac8bb138751db24d4b8aabe4edc2a9870f6796e40e337f9f7a5e5139/_data","Destination": "/data_1","Driver": "local","Mode": "","RW": true,"Propagation": ""}
### 可以看到 Source 是映射的目录, Destination 是我们的数据卷.
/var/lib/docker/volumes/102d3cd7ac8bb138751db24d4b8aabe4edc2a9870f6796e40e337f9f7a5e5139/_data
在 Source 位置创建一个文件, 检查是否出现.
##
# sudo touch /var/lib/docker/volumes/102d3cd7ac8bb138751db24d4b8aabe4edc2a9870f6796e40e337f9f7a5e5139/_data/1.test## 检查是否出现1.test
# sudo docker attach a41e30
/ # ls
bin data_1 dev etc home proc root sys tmp usr var
/ # cd data_1/
/data_1 # ls ### 可以看到 1.test
1.test
/data_1 #
Volume 的作用,它可以让我们本机和我们的容器实现目录的映射,从而达到数据共享的目的,
手动映射容器数据卷到本地目录
-v 命令也可以让我们指定我们本机的哪一个目录映射到我们容器的什么目录。
在之前我们使用-v的时候,大家回忆一下,我们只是指定了容器内的挂载点,那么它对应到本机的什么地址,实际上是由Docker自动帮我们来决定的。
## 把本机目录的data2目录映射到新建的容器的 data2 目录。## 窗口1 操作容器
# sudo docker run --rm -it -v ~/data_2:/data_2 busybox ### 命令会后直接进入容器
/ # ls
bin data_2 dev etc home proc root sys tmp usr var
/ # cd data_2/ ### 进入容器中的 data2目录
/data_2 # ls
test.2
/data_2 # ## 窗口2 操作本地
root@ubuntu-xenial:~# cd data_2/
root@ubuntu-xenial:~/data_2# ls ### 窗口 1 创建完test.2文件后查看本地
root@ubuntu-xenial:~/data_2# touch test.2 ### 本地 data2 目录里已经有这个文件## 同理, 在 docker 中的 data2中创建一个 test.3 文件, 三个都可以看到.
接下来我会给大家介绍如何使用数据卷容器,在容器和主机容器和容器之间共享数据,并且实现数据的备份和恢复。首先我们来了解数据卷
Docker的数据管理-数据卷容器
如果用户需要在容器之间共享一些持续更新的数据,最简单的方式就是使用数据卷容器。
概念: 数据卷容器顾名思义,它其实是一个普通的容器,只不过它的目的就是专门提供数据卷供其他的容器来使用
首先我会创建一个数据卷容器,我给它起一个名字 dbdata1,并且我会在其中创建一个数据卷,挂载到 dbdata_1 数据卷容器的根目录下. 然后分别创建两个普通容器, 这两个容器通过参数 --volume-from 使用 dabdata_1 容器作为数据卷容器. 最后分别在如上三个容器中分别创建 test.1, test.2, test.3, 通过 ls 命令观察同步成功的情况.
#### 一共三个窗口, 通过创建三个文件以便观察数据同步的情况
## 窗口1. 数据卷容器 dbdata_1$ sudo docker run -it -v /dbdata --name dbdata_1 busybox
$ ls
bin dbdata dev etc home proc root sys tmp usr var
$ cd dbdata/ ### 三个窗口同时进入数据卷容器的目录
$ /dbdata # touch test.1 ### 三个窗口分别创建一个测试文件 test.1
$ /dbdata # ls
test.1 test.2 test.3 ### 最终看到分别创建的文件同时出现在每个窗口/dbdata # #### 窗口 2 和窗口 3 是数据容器, 这两者将把窗口1 的容器作为数据卷
二者操作完全相同, 可以实时展现数据的同步## 窗口 2 普通容器 db1
$sudo docker run -it --volumes-from dbdata_1 --name db1 busybox
### 参数- -volujmes-from 是告知db1容器要使用 dbdata_1 容器作为一个数据卷$ # ls
bin dev home root tmp var
dbdata etc proc sys usr
$ # cd dbdata/ ### 三个窗口同时进入数据卷容器的目录
$ /dbdata # touch test.2 ### 三个窗口分别创建一个测试文件 test.2
$ /dbdata # ls
test.1 test.2 test.3 ### 最终看到分别创建的文件同时出现在每个窗口## 窗口 3 普通容器 db2
$sudo docker run -it --volumes-from dbdata_1 --name db2 busybox$ # ls
bin dev home root tmp var
dbdata etc proc sys usr
$ # cd dbdata/ ### 三个窗口同时进入数据卷容器的目录
$ /dbdata # touch test.3 ### 三个窗口分别创建一个测试文件 test.3
$ /dbdata # ls
test.1 test.2 test.3 ### 最终看到分别创建的文件同时出现在每个窗口
本章完毕.
6 端口映射实现访问容器
由于使用的是 vagrant 虚拟机, 因此需要二次映射, 8080-8080, 8080:8288(物理机端口)
sudo docker port 容器名
7. Docker File
Docker File是什么?
实际上它是一个文本格式的配置文件,我们的用户可以使用Docker File 来快速创建自定义的镜像。在实际工作中我们经常会遇到现有的镜像,并不能满足我们需求这样的情况,这种情况下就比较适合我们使用Docker file来自己快速的构建镜像
我们首先会大致的讲一下 Docker File 有哪些组成部分。然后我们针对Docker file的命令进行一个详细的解释。最后我们再给大家展示一些经典的案例。
Docker 命令参考连接: Docker 命令详解
Github 案例链接: 使用 From scratch 从零开始构建 ubuntu Docker 镜像
本章节目录结构:
Dockerfile 创建在 /vagrant/data 目录中
简单来说, From 命令常用方法是, 引用的 Docker 镜像源头来自哪里.
Run 命令是构建 docker 中运行的命令, 比如 apt-get update 或者 yum install..
Docker File 指令
FROM
FROM <image> --- Docker Image 的 ID.
FROM FROM <image>:<tag>
FROM <image>:<digest>
通过 FROM 指定的镜像,可以是任何有效的基础镜像。FROM 有以下限制:
- FROM 必须 是 Dockerfile 中第一条非注释命令.
- 在一个 Dockerfile 文件中创建多个镜像时,FROM 可以多次出现。只需在每个新命令 FROM 之前,记录提交上次的镜像 ID。
- tag 或 digest 是可选的,如果不使用这kkkkkkkkkk两个值时,会使用 latest 版本的基础镜像
创建一个镜像, 基于最小的 linux docker image: Alpine.
## 创建一个基于 alpine 的镜像$ FROM alpine:3.4
2 RUN 执行命令
在镜像的构建过程中执行特定的命令,并生成一个中间镜像。格式:
#shell格式
RUN <command>
#exec格式
RUN ["executable", "param1", "param2"]
- RUN 命令将在当前 image 中执行任意合法命令并提交执行结果。命令执行提交后,就会自动执行 Dockerfile 中的下一个指令。
- 层级 RUN 指令和生成提交是符合 Docker 核心理念的做法。它允许像版本控制那样,在任意一个点,对 image 镜像进行定制化构建。
- RUN 指令创建的中间镜像会被缓存,并会在下次构建中使用。如果不想使用这些缓存镜像,可以在构建时指定
--no-cache
参数,如:docker build --no-c
创建上述实验中的
自定义一个镜像
基于国情, 我们构建镜像时, 会遇到速度过慢的问题, 解决方案有两个:
1. 换中科大源, 这里在 dockerfile 中添加了一条 RUN命令.
`RUN sed -i 's/http://dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories `
2. 使用代理: 具体参考 Segmentfault--在国内 docker build 的正确姿势
## 基础工作:
##在 vagrant 目录中创建 data 目录, 使用 touch 命令创建 vagrant file.
ssh 登录 vagrant
$ cd /vagrant
$ mkdir data
$ cd data
$ touch Dockerfile ### Dockerfile 第一个字母 D 大小写均可
$ vim Dockerfile ### dockerfile 文件名是创建 docker 默认的名字## Dockerfile 写入如下内容:FROM alpine:3.4
RUN apk update
RUN apk add vim
RUN apk add curl## 使用 Docker build 命令创建镜像
$ sudo docker build . -t test/apline-master:v1.0
我们在虚拟机环境里来构建,首先我们必须要处于 Docker file 的目录下,然后我们可以使用Docker的build命令,-t 用来打 tag. tag 名为: test/apline-master:v1.0 的版本, 敲击回车 。这里我还需要加上一个点表示,在当前我们的目录下去寻找 docker file 文件, 回车,可以看到我们的构建已经开始了,这里大家可以看有 step的信息.
命令 sudo docker build . -t test/apline-master:v1.0 解析: 点的意思是在当前目录下查找 Dockerfile文件, -t 的意思是打标签.
vagrant@ubuntu-xenial:/vagrant/data$ sudo docker build . -t test/apline-master:v1.0
Sending build context to Docker daemon 2.048kB
Step 1/4 : FROM alpine:3.4
3.4: Pulling from library/alpine ### 第一步是下载, 因为本地目录没有 alpine 镜像.
c1e54eec4b57: Pull complete
Digest: sha256:b733d4a32c4da6a00a84df2ca32791bb03df95400243648d8c539e7b4cce329c
Status: Downloaded newer image for alpine:3.4---> b7c5ffe56db7
Step 2/4 : RUN apk update---> Running in 70cba0f959b7
fetch http://dl-cdn.alpinelinux.org/alpine/v3.4/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.4/community/x86_64/APKINDEX.tar.gz
v3.4.6-316-g63ea6d0 [http://dl-cdn.alpinelinux.org/alpine/v3.4/main]
v3.4.6-160-g14ad2a3 [http://dl-cdn.alpinelinux.org/alpine/v3.4/community]
OK: 5973 distinct packages available
Removing intermediate container 70cba0f959b7---> 0c582eedd507
Step 3/4 : RUN apk add vim---> Running in d42d3c2565d4
(1/5) Installing lua5.2-libs (5.2.4-r2)
(2/5) Installing ncurses-terminfo-base (6.0_p20171125-r0)
(3/5) Installing ncurses-terminfo (6.0_p20171125-r0)
(4/5) Installing ncurses-libs (6.0_p20171125-r0)
(5/5) Installing vim (7.4.1831-r3)
Executing busybox-1.24.2-r14.trigger
OK: 37 MiB in 16 packages
Removing intermediate container d42d3c2565d4---> 4f37142301d3
Step 4/4 : RUN apk add curl---> Running in 6b2cb98ee1a8
(1/4) Installing ca-certificates (20161130-r0)
(2/4) Installing libssh2 (1.7.0-r0)
(3/4) Installing libcurl (7.60.0-r1)
(4/4) Installing curl (7.60.0-r1)
Executing busybox-1.24.2-r14.trigger
Executing ca-certificates-20161130-r0.trigger
OK: 38 MiB in 20 packages
Removing intermediate container 6b2cb98ee1a8---> e4f2fa044d6a
Successfully built e4f2fa044d6a
Successfully tagged test/apline-master:v1.0
## 查看刚刚创建的镜像
## 查看刚刚创建的镜像$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
test/apline-master v1.0 e4f2fa044d6a 3 minutes ago 32.2MB
为什么要把这个文件名字命名为Dockerfile,
可以看到在我们的构建命令中,我并没有显示的指定去执行哪一个,去构建哪一个Dockerfile 文件。那么默认情况下,它会在我们当前的目录去寻找名字为 Dockefile 的这么一个构建文件。如果存在构建就开始.如果我们把构件文件改成其他的名字,那么我们这个构建指令就会失效。我们就必须要显示的指定,我们要使用我们要构建哪一个Dockerfile,所以说这就是为什么我们把配置文件命名为Dockerfile.
镜像分层的概念
先接上一节, 检查我们只做好的镜像
## 进入test/apline镜像并检查$ sudo docker run --rm -it e4f2fa044d6a /bin/sh
/ # vim --v ### 加上 --rm参数在执行完毕之后,我们就删除这个容器
VIM - Vi IMproved 7.4 (2013 Aug 10, compiled Feb 16 2017 11:25:35)
Unknown option argument: "--v"
More info with: "vim -h"
/ # curl --version
curl 7.60.0 (x86_64-alpine-linux-musl) libcurl/7.60.0 OpenSSL/1.0.2n zlib/1.2.11 libssh2/1.7.0
Release-Date: 2018-05-16
Protocols: dict file ftp ftps gopher http https imap imaps pop3 pop3s rtsp scp sftp smb smbs smtp smtps telnet tftp
Features: AsynchDNS IPv6 Largefile NTLM NTLM_WB SSL libz TLS-SRP UnixSockets HTTPS-proxy
5. 镜像分层和Cache
## -a 参数vagrant@ubuntu-xenial:/vagrant/data$ sudo docker images -a
REPOSITORY TAG IMAGE ID CREATED SIZE
test/apline-master v1.0 e4f2fa044d6a 23 minutes ago 32.2MB
<none> <none> 4f37142301d3 23 minutes ago 30.6MB
<none> <none> 0c582eedd507 23 minutes ago 5.58MB### 上面两个 none 标签的 image,就是过渡 image
好,接下来我们来谈一下镜像分层的概念。
刚才我们在提到 run 指令的时候,我说了每执行 run 指令,我们的镜像就会提交一次. 当我们去执行 Docker image -a, -a 参数会罗列出我们当前当前本地镜像库所有的镜像,包括最终镜像和中间镜像。
我们 Dockerfile 中有三个run指令,也就是说他提交了三次,也就是说在整个构建的过程中会构建出三个镜像。大家可以看从5.58, 30.6 32.2M,这就是我们构建出来的三个镜像。每一个镜像都和前面的镜像有关系。比如说大小为30.6兆的镜像,就保存了和下面5.58兆镜像不同的信息。那么上面的32.2兆的镜像就保存了和30.6兆镜像不同的信息。那么最终我们看到的实际上是最上面的镜像,但是这两个镜像实际上在我们整个构建过程中也是被构建出来了。
那么Docker 为什么要保留这样两个镜像?其实刚才我基本上也说到了,他只会保留和前一个镜像,也只是所谓副镜像不一样的信息,这个和我们在使用版本控制软件的概念其实是一样的,我们每一次的提交只提交了变动而已,而不是提交整个文件,这样的话会节省我们的空间。另外有一点 Dockerfile 会利用 Docker 里面有一个叫做镜像缓存的机制,既然是缓存,顾名思义就是为了复用。我们如果这次比方说这里有一次构建,它就要构建出三层,那么他发现前两层在我们的缓存里已经存在了,那就会直接从我们的缓存中取出已经构建好的镜像,避免重复构建,然后把不同的地方再单独构建一次,加速构建效率。
通过再构建一个镜像, 观察 Docker Cache 的复用效果
## 更改 Dockerfile 内容$ vim Dockerfile### 内容如下
FROM alpine:3.4
RUN apk update
RUN apk add curl
RUN apk add vim
RUN apk add git## 创建新镜像 v2.0
$ sudo docker build . -t test/apline-master:v2.0
## 创建 v2.0 过程
sudo docker build . -t test/apline-master:v2.0
Sending build context to Docker daemon 3.072kB
Step 1/5 : FROM alpine:3.4---> b7c5ffe56db7
Step 2/5 : RUN apk update---> Using cache ### 直接使用 b7c5ff 的 cache 镜像(中间镜像)---> 0c582eedd507
Step 3/5 : RUN apk add curl---> Running in 87c9cfc43f98
(1/4) Installing ca-certificates (20161130-r0)
(2/4) Installing libssh2 (1.7.0-r0)
(3/4) Installing libcurl (7.60.0-r1)
(4/4) Installing curl (7.60.0-r1)
Executing busybox-1.24.2-r14.trigger
Executing ca-certificates-20161130-r0.trigger
OK: 6 MiB in 15 packages
Removing intermediate container 87c9cfc43f98---> 0661fdb17dd1
Step 4/5 : RUN apk add vim---> Running in e626c02651bd
(1/5) Installing lua5.2-libs (5.2.4-r2)
(2/5) Installing ncurses-terminfo-base (6.0_p20171125-r0)
(3/5) Installing ncurses-terminfo (6.0_p20171125-r0)
(4/5) Installing ncurses-libs (6.0_p20171125-r0)
(5/5) Installing vim (7.4.1831-r3)
Executing busybox-1.24.2-r14.trigger
OK: 38 MiB in 20 packages
Removing intermediate container e626c02651bd---> 3f7373634309
Step 5/5 : RUN apk add git---> Running in 18507f2b4abc
(1/3) Installing expat (2.2.0-r1)
(2/3) Installing pcre (8.38-r1)
(3/3) Installing git (2.8.6-r0)
Executing busybox-1.24.2-r14.trigger
OK: 54 MiB in 23 packages
Removing intermediate container 18507f2b4abc---> 757844a1c97f
Successfully built 757844a1c97f
Successfully tagged test/apline-master:v2.0### 由于 Dockerfile 中更改了同样两条命令的顺序, 因此它并不能使用 cache 镜像,
而是直接重新创建中间镜像, 并删除了重复的镜像(理解如此, 未检查)
### 如何实现一次提交
## 同时提交多条 RUN 命令## 第一种
FROM alpine:3.4
RUN apk update && apk add vim && apk add git && apk add curl## 第二种FROM alpine:3.4
RUN apk update && apk add
vim
git
curl
## 在 ubuntu 下安装 docker 环境并创建 v3.0sudo docker build . -t test/apline-master:v3.0
Sending build context to Docker daemon 3.072kB
Step 1/2 : FROM alpine:3.4---> b7c5ffe56db7
Step 2/2 : RUN apk update && apk add curl vim git---> Running in e965f7dc4913
fetch http://dl-cdn.alpinelinux.org/alpine/v3.4/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.4/community/x86_64/APKINDEX.tar.gz
v3.4.6-316-g63ea6d0 [http://dl-cdn.alpinelinux.org/alpine/v3.4/main]
v3.4.6-160-g14ad2a3 [http://dl-cdn.alpinelinux.org/alpine/v3.4/community]
OK: 5973 distinct packages available
(1/12) Installing ca-certificates (20161130-r0)
(2/12) Installing libssh2 (1.7.0-r0)
(3/12) Installing libcurl (7.60.0-r1)
(4/12) Installing curl (7.60.0-r1)
(5/12) Installing expat (2.2.0-r1)
(6/12) Installing pcre (8.38-r1)
(7/12) Installing git (2.8.6-r0)
(8/12) Installing lua5.2-libs (5.2.4-r2)
(9/12) Installing ncurses-terminfo-base (6.0_p20171125-r0)
(10/12) Installing ncurses-terminfo (6.0_p20171125-r0)
(11/12) Installing ncurses-libs (6.0_p20171125-r0)
(12/12) Installing vim (7.4.1831-r3)
Executing busybox-1.24.2-r14.trigger
Executing ca-certificates-20161130-r0.trigger
OK: 54 MiB in 23 packages
Removing intermediate container e965f7dc4913---> 72527ccfc4ef
Successfully built 72527ccfc4ef
Successfully tagged test/apline-master:v3.0
使用命令检查
## 检查
$ sudo docker images -a ### 可以看到只有一层, 因为我们只进行了一次提交
REPOSITORY TAG IMAGE ID CREATED SIZE
test/apline-master v3.0 72527ccfc4ef 45 seconds ago 48.5MB
test/apline-master v2.0 757844a1c97f 13 minutes ago 48.7MB
<none> <none> 3f7373634309 13 minutes ago 32MB
<none> <none> 0661fdb17dd1 13 minutes ago 7.01MB
test/apline-master v1.0 e4f2fa044d6a 12 hours ago 32.2MB
<none> <none> 4f37142301d3 12 hours ago 30.6MB
<none> <none> 0c582eedd507 12 hours ago 5.58MB
API 镜像的构建
## 构建镜像# This is used for building Restful API
FROM fabric8/java-alpine-openjdk8-jdk ### 第一句非注释语句必须是 FROM 指令
LABEL maintainer ="test.docker@test.com" version="1.0"
### 维护作者信息的指令, 通过 inspect 命令查看
COPY target/dockerdemo1-8.0.1-SNAPSHOT.jar /app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"
构建镜像
## 构建镜像
sudo docker build . -t restful_api:v6REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> 83ea132f3063 40 seconds ago 108MB
<none> <none> 9f2873387662 11 minutes ago 108MB
### 命令构建完成, 无法打上标签, 原因不明## 检查镜像完成情况$ sudo docker images -a
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> ### 这行的镜像废了 <none> 83ea132f3063 40 seconds ago 108MB
<none> <none> 9f2873387662 11 minutes ago 108MB## 检查 Label 命令的结果$ sudo docker inspect 9f2873387662 | grep Labels -A 3"Labels": {"maintainer": "=test.docker@test.com version=1.0"}},
--"Labels": {"maintainer": "=test.docker@test.com version=1.0"}},
移除 COPY 指令, 创建 V8
## 创建 V8$ sudo docker build . -t restful_api:v8
Sending build context to Docker daemon 3.072kB
Step 1/4 : FROM fabric8/java-alpine-openjdk8-jdk---> 2b7844efe720
Step 2/4 : LABEL maintainer="SvenDowideit@home.org.au" version="1.0"---> Running in 59ba789c8740
Removing intermediate container 59ba789c8740---> c90062fc7580
Step 3/4 : EXPOSE 8080---> Running in 6cd4713a80d2
Removing intermediate container 6cd4713a80d2---> 7bbb1fa1b39e
Step 4/4 : ENTRYPOINT ["java","-jar","/app.jar"---> Running in 3dc9bf9295ce
Removing intermediate container 3dc9bf9295ce---> 8d1c8d2fa5fe
Successfully built 8d1c8d2fa5fe
Successfully tagged restful_api:v8## 检查 V8, 终于正常的打上了标签. 不知为何生成了两个中间镜像
sudo docker images -a
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> 7bbb1fa1b39e 53 seconds ago 108MB
<none> <none> c90062fc7580 53 seconds ago 108MB
restful_api v8 8d1c8d2fa5fe 53 seconds ago 108MB
<none> <none> 9f2873387662 17 hours ago 108MB## 通过 Inspect 观察指令
sudo docker inspect 8d1c8d2fa5fe | grep Labels -A 3"Labels": {"maintainer": "SvenDowideit@home.org.au","version": "1.0"}
--"Labels": {"maintainer": "SvenDowideit@home.org.au","version": "1.0"}
maintainer 仅限于作者, Label 可以有多重信息
## Dockerfile 内容# Thi is used for building Restful API
FROM fabric8/java-alpine-openjdk8-jdk
# MAINTAINER SvenDowideit@home.org.au
LABEL maintainer="SvenDowideit@home.org.au" version="1.0" name="SvenDovideit"
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"
COPY 和 ADD 指令
一般不推荐使用 ADD 指令, 大的原则是越原始的命令, 越可靠. 实际工作中, 你不知道 ADD 指令执行了什么, 卡在什么地方了. 对于排查问题很困难.
## 标准格式## COPY 命令
COPY <源路径>... <目标路径>
COPY ["<源路径1>",... "<目标路径>"]## ADD 命令ADD <源路径>... <目标路径>
ADD ["<源路径>",... "<目标路径>"]
ADD 指令和 COPY 的格式和性质基本一致。但是在 COPY 基础上增加了一些功能。比如<源路径>
可以是一个 URL,这种情况下,Docker 引擎会试图去下载这个链接的文件放到<目标路径>
去。
COPY 指令是用来把我们本地的文件复制到我们容器中
## 镜像中存在一条 COPY 命令的示例:
COPY target/dockerdemo1-0.0.1-SNAPSHOT.jar /app.jar### 在我们这个例子里,我们把我们本地target目录下的jar 包,dockerdemo,复制到了我们的容器根目录下,并且我们给 jar 包重新起了一个名字叫做 app.jar
WORKDIR 命令, 指定工作目录
WORKDIR用于在容器内设置一个工作目录:
## 格式
WORKDIR /path/to/workdir
通过WORKDIR设置工作目录后,Dockerfile 中其后的命令 RUN、CMD、ENTRYPOINT、ADD、COPY 等命令都会在该目录下执行。
# Dockerfile 示例# Thi is used for building Restful API
FROM fabric8/java-alpine-openjdk8-jdk
# MAINTAINER SvenDowideit@home.org.au
LABEL maintainer="SvenDowideit@home.org.au" version="1.0" name="SvenDovideit"WORKDIR /
COPY target/dockerdemo1-0.0.1-SNAPSHOT.jar ./app.jar
### 增加了一个点, 意思是复制到当前目录下, 当前目录由于 WORKDIR定义, 当前目录为根目录.
因此, 实现的效果和下面被井号注释掉的 COPY 命令一样效果. 但是如果 WORKDIR/home , 那么
当前目录就为 /home 了.# COPY target/dockerdemo1-0.0.1-SNAPSHOT.jar /app.jarEXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"
EXPOSE 命令
为构建的镜像设置监听端口,使容器在运行时监听。格式:
## 格式
EXPOSE <port> [<port>...]
Stackoverflow 的 Docker 开放3000, 443, 22, 80 4个端口的例子
需要注意的是我们 expose端口不仅仅能发布一个端口,我们同样可以发布多个端口,那么这里大家可以看在stackoverflow上有这么一个例子,在这个例子里它发布了4个端口,它这4个端口也是根据应用内部的需要来发布的。
比如说如果我们开放了22端口,我们就可以通过ssh来访问这个容器,
再比如说我们容器的内部安装了阿帕奇的这么一个外部服务器,那么阿帕奇的外部服务器默认端口为80,所以这里有意识的把镜像的80端口可以暴露出去,以此类推端暴露哪些端口,就可以根据我们用户的需求来定制
当我们使用-p参数, -小p参数的时候来指定的时候,我们可以显示的指定我们本机的哪个端口和我们容器的哪一个端口来进行映射。
当我们使用-大P参数的时候,我们就让我们的 Docker 给我们随机分配本地端口去映射我们容器的端口。
参考上一节: 端口映射实现访问容器的课程
## 7 VOLUME 定义匿名卷
VOLUME用于创建挂载点,即向基于所构建镜像创始的容器添加卷:
## 格式
VOLUME ["/data"]
指定本机特定目录到容器目录
## 首先 build 这个容器
$ sudo docker build . -t restful_api:v13## 在dockerfile 中创建 工作目录$ vagrant@ubuntu-xenial:/vagrant/data/data$ vim Dockerfile
## 查看 vim 的内容
# This is used for building Restful API
FROM fabric8/java-alpine-openjdk8-jdk# MAINTAINER SvenDowideit@home.org.au
LABEL maintainer="SvenDowideit@home.org.au" version="1.0" name="SvenDovideit"WORKDIR /COPY target/dockerdemo1-0.0.1-SNAPSHOT.jar ./app.jarVOLUME ["/data1"]EXPOSE 8080CMD java -jar /app.jar# ENTRYPOINT ["java","-jar","/app.jar"]
运行并检查
## 根据创建的 Dockerfile 生成镜像
$ vagrant@ubuntu-xenial:/vagrant/data/data$ sudo docker build . -t restful_api:v13## 检查 image 的创建结果
$ vagrant@ubuntu-xenial:/vagrant/data/data$ sudo docker images -a
vagrant@ubuntu-xenial:/vagrant/data/data$ sudo docker images -a
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> 91abb9ac9f50 11 seconds ago 125MB
restful_api v13 6f46f73ccd15 11 seconds ago 125MB## 通过 image 的 ID 运行容器
$ sudo docker run -it -v /tmp/localdata:/data1 6f46f73ccd15 /bin/sh## 检查容器的 ID. 可以看到容器和镜像都有各自的 ID:
$ vagrant@ubuntu-xenial:/vagrant/data/data$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
85e97f37db33 6f46f73ccd15 "/bin/sh" 2 minutes ago Exited (0) 10 seconds ago sharp_ellis## 检查容器中 data1 的源目录 -- 即在本机中的位置
$ vagrant@ubuntu-xenial:/vagrant/data/data$ sudo docker inspect 85e97f37db33 | grep Mounts -A 10## 使用 grep Mounts -A 10 找到 Mounts 关键字后的 10 行, 注意区分大小写"Mounts": [{"Type": "bind","Source": "/tmp/localdata","Destination": "/data1","Mode": "","RW": true,"Propagation": "rprivate"}],"Config": {## 这个地址是由 Docker 自动给我们生成的这么一个地址
## 指定 data1 的本地目录命令
$ sudo docker run -it -v /tmp/localdata:/data1 6f46f73ccd15 /bin/sh
## -v 参数是 volume, 指定本地 /tmp/localdat 的目录为 6f46f73ccd15 镜像的 data1 的目录
在 data1 目录中创建一个 test.txt 文档, 检查是否在对应的本地目录 /tmp/localdata 中出现
## 检查 test.txt 的存在
vagrant@ubuntu-xenial:/vagrant/data/data$ sudo docker run -it -v /tmp/localdata:/data1 6f46f73ccd15 /bin/sh
/ # ls
app.jar data1 dev home media opt root sbin sys usr
bin deployments etc lib mnt proc run srv tmp var
/ # cd data1/
/data1 # ls
/data1 # touch test.txt
/data1 # ls
test.txt
/data1 # exit
vagrant@ubuntu-xenial:/vagrant/data/data$ ls /tmp/localdata/
test.txt ## 可以看到在 data1 中创建的文件, 出现在本地目录/tmp/localdata 下.## 在本地目录中创建一个文件, 看是否可以出现在容器的/data1 目录下
$ vagrant@ubuntu-xenial:/vagrant/data/data$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3f4519766cca 6f46f73ccd15 "/bin/sh" 3 minutes ago Exited (0) 2 minutes ago silly_grothendieck
### 容器的 ID 为 3f4519766cca
$ vagrant@ubuntu-xenial:/vagrant/data/data$ sudo docker exec -it 3f4519766cca /bin/sh
/ # ls /data1
test.txt test2.txt
### 通过 exec 命令进入容器, 并检查 /data1 中, 发现 test2.txt存在.
上图是视频截图.
ENV 指令
env 指令,这个指令是用来设置环境变量的,这个环境变量被设置好了之后,无论是我们在构建镜像的过程中,还是在容器启动的过程中,它都是存在的,我们都可以来引用.
ENV AUTHOR="SvenDovideit"
创建V14镜像
## 创建容器$ vagrant@ubuntu-xenial:/vagrant/data/data$ sudo docker build . -t restful_api:v14
查看得知 images ID 为 28d0
$ sudo docker run -it 28d03fe91988 /bin/sh## 进入容器查看 ENV 指令内容
$ vagrant@ubuntu-xenial:/vagrant/data/data$ sudo docker run -it 28d03fe91988 /bin/sh
/ # echo $AUTHOR
SvenDovideit
/ #
环境变量之 ARG 指令
ARG 指令和 ENV 指令是非常相像的,它们唯一的不同就在于ARG 指令是用在构建时候的变量,也就是说当我们构建完成之后,我们通过 ARG 指令设定的环境变量就无法再访问了。
从下图实验可以看出, 创建完成后, 无法访问 ARG 创建的 build_user, 它仅仅在创建 docker 时使用, 过后无法访问. (和 ENV太像, 未做测试, 下图为他人实验截图)
ENTRYPOINT 指令
## 两种格式, 第一种格式叫做 EXEC 格式。第二种格式叫做 SHELL 格式.
ENTRYPOINT ["executable", "param1", "param2"]
ENTRYPOINT command param1 param2
ENTRYPOINT 指令,这个指令用于给我们的容器配置一个可执行的程序。也就是说每次我们在使用镜像创建容器的时候,我们是通过ENTRYPOINT指定的程序来设置我们的默认启动程序。
Dockerfile 中可以有多个 ENTRYPOINT 指令,但而只有最后一条 ENTRYPOINT会执行,前面的都会被覆盖
ENTRYPOINT 与 CMD 非常类似,不同的是通过docker run
执行的命令不会覆盖 ENTRYPOINT,而docker run
命令中指定的任何参数,都会被当做参数再次传递给 ENTRYPOINT。Dockerfile 中只允许有一个 ENTRYPOINT 命令,多指定时会覆盖前面的设置,而只执行最后的 ENTRYPOINT 指令。docker run
运行容器时指定的参数都会被传递给 ENTRYPOINT ,且会覆盖 CMD 命令指定的参数。如,执行docker run <image> -d
时,-d 参数将被传递给入口点。
### 完整构建代码:
# Version: 0.0.3
FROM ubuntu:16.04
MAINTAINER 何民三 "cn.liuht@gmail.com"
RUN apt-get update
RUN apt-get install -y nginx
RUN echo 'Hello World, 我是个容器' > /var/www/html/index.html
ENTRYPOINT ["/usr/sbin/nginx"]
EXPOSE 80
#### 使用docker build构建镜像,并将镜像指定为 itbilu/test:
docker build -t="itbilu/test" .
#### 构建完成后,使用itbilu/test启动一个容器:
docker run -i -t itbilu/test -g "daemon off;"
在运行容器时,我们使用了-g "daemon off;"
,这个参数将会被传递给 ENTRYPOINT,最终在容器中执行的命令为/usr/sbin/nginx -g "daemon off;"
。
CMD 指令
## CMD 指令的有三种格式,
## 第一种叫做exec格式和 ENTRYPOINT 是一样的,
## 第二种叫做parameter格式,也就是所谓的参数格式。
## 第三种叫做shell格式,也就是所谓的命令行格式,CMD ["executable","param1","param2"]
CMD ["param1","param2"]
CMD command param1 param2
第二种格式可以使我们的cmd中的参数被当作 ENTRYPOINT的默认参数,此时的ENTRYPOINT必须得是exec格式,也就是说他们两个之间有一些微妙的搭配
CMD ["-jar","/app.jar"] ## CMD 参数格式下, 会默认传递参数给下一行的 ENTRYPOINT 命令
ENTRYPOINT ["java"]
## Github 案例1讲解: 使用 From scratch 从零开始构建 ubuntu Docker 镜像
CMD ["/bin/bash"]
在我们实际的工作中,我们一般不会从零开始来构建镜像,我们一般会选做选一个镜像作为我们的基础镜像,在它之上我们来进行一些构建。比如说我们想构建一个基于 ubuntu 的一个开发环境,我们不会使用from scratch,我们会使用from ubuntu 然后选定一个版本,然后在这个已经安装好的安装和 ubuntu 操作系统的镜像里,我们执行一些操作,比如说安装一个 tomcat的软件,安装get 安装Docker等等。然后我们把制作好的镜像作为基础镜像分发出去,供我们 team内部的成员使用,然后大家在这个基础上进行开发工作,最后我们把镜像分发给我们的测试人员和运维人员,基本上是这样一个流程
## Github 案例 2讲解: 一个TOMCAT 容器构建
## 如下内容和 tomcat 服务器相关, 非关注重点
# let "Tomcat Native" live somewhere isolated
ENV TOMCAT_NATIVE_LIBDIR $CATALINA_HOME/native-jni-lib
ENV LD_LIBRARY_PATH ${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$TOMCAT_NATIVE_LIBDIR# see https://www.apache.org/dist/tomcat/tomcat-$TOMCAT_MAJOR/KEYS
# see also "update.sh" (https://github.com/docker-library/tomcat/blob/master/update.sh)
ENV GPG_KEYS 05AB33110949707C93A279E3D3EFE6B686867BA6 07E48665A34DCAFAE522E5E6266191C37C037D42 47309207D818FFD8DCD3F83F1931D684307A10A5 541FBE7D8F78B25E055DDEE13C370389288584E7 61B832AC2F1C5A90F0F9B00A1C506407564C17A3 79F7026C690BAA50B92CD8B66A3AD3F4F22C4FED 9BA44C2621385CB966EBA586F72C284D731FABEE A27677289986DB50844682F8ACB77FC2E86E29AC A9C5DF4D22E99998D9875A5110C01C5A2F6059E7 DCFD35E0BF8CA7344752DE8B6FB21E8933C60243 F3A04C595DB5B6A5F1ECA43E3B7BBB100D811BBE F7DA48BB64BCB84ECBA7EE6935CD23C10D498E23ENV TOMCAT_MAJOR 9
ENV TOMCAT_VERSION 9.0.31
ENV TOMCAT_SHA512 75045ce54ad1b6ea66fd112e8b2ffa32a0740c018ab9392c7217a6dd6b829e8645b6810ab4b28dd186c12ce6045c1eb18ed19743c5d4b22c9e613e76294f22f5
这个文件末尾
EXPOSE 8080
CMD ["catalina.sh", "run"]
全文完, 后续视情况更新.
错误:
restful风格的API, openjdk:8-jdk-alpine 已不存在,
替代镜像:fabric8/java-alpine-openjdk8-jdk
改进:
在镜像中加入 RUN 命令, 替换国内源.
未知:
dockerdemo1-0.0.1-SNAPSHOT.jar 是啥, 相关的使用 8080 端口的程序是啥
问题:
1. vagrant 中 copy 文件的问题
## 解决拷贝文件到 vagrant 环境的问题方法 1: 在 vagrant up 之后的环境中运行如下命令
$ vagrant plugin install vagrant-scp## 使用方法
vagrant scp <some_local_file_or_dir> [vm_name]:<somewhere_on_the_vm>## 方法 2: 在 macOS 默认 Terminal 环境下即可
cp -R 递归目录拷贝.
通过 pwd 找到 vagrantfile 所在文件夹(目标文件夹)的位置.
通过 FINDer 中直接拖曳目录到 Terminal 中的方式获得源文件夹的位置$ cp -R cp -R /Users/mask/Documents/需要copy 的文件夹/1. 介绍/1.1 Archive.zip/env/data /Users/mask#### 检查 copy 的结果$ vagrant ssh
$ vagrant@ubuntu-xenial:/vagrant/data$ pwd
/vagrant/data ## 当前目录 , 这个目录就是虚拟机运行时,vagrant 的目录
2. 无法运行 localhost:8280/hi
## Dockerfile 文件内容如下
# This is used for building Restful API
FROM fabric8/java-alpine-openjdk8-jdk# MAINTAINER SvenDowideit@home.org.au
LABEL maintainer="SvenDowideit@home.org.au" version="1.0" name="SvenDovideit"
WORKDIR /
COPY target/dockerdemo1-0.0.1-SNAPSHOT.jar ./app.jar
VOLUME ["/data1"]
EXPOSE 8080ENTRYPOINT ["java","-jar","/app.jar"]
创建 V7版 image 并查看 image ID 为2ee2b7f5a817
$ vagrant@ubuntu-xenial:/vagrant/data/data$ sudo docker build . -t restful_api:v7
$ vagrant@ubuntu-xenial:/vagrant/data/data$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
restful_api v7 2ee2b7f5a817 14 seconds ago 125MB
restful_api v13 6f46f73ccd15 3 hours ago 125MB
运行 2ee2b7f5a817 的容器之后可以看到 java 启动的信息, 但是无法通过http://localhost:8080/hi 或者http://localhost:8280/hi, 或者http://localhost:8080, 或者 http://localhost:8280, 均无法打开网页
$ vagrant@ubuntu-xenial:/vagrant/data/data$ sudo docker run -p 8080:8080 2ee2b7f5a817. ____ _ __ _ _/ / ___'_ __ _ _(_)_ __ __ _
( ( )___ | '_ | '_| | '_ / _` | / ___)| |_)| | | | | || (_| | ) ) ) )' |____| .__|_| |_|_| |___, | / / / /=========|_|==============|___/=/_/_/_/:: Spring Boot :: (v2.1.6.RELEASE)2020-09-03 10:23:32.133 INFO 1 --- [ main] c.a.dockerdemo1.Dockerdemo1Application : Starting Dockerdemo1Application v0.0.1-SNAPSHOT on e17e19a2e114 with PID 1 (/app.jar started by root in /)
2020-09-03 10:23:32.138 INFO 1 --- [ main] c.a.dockerdemo1.Dockerdemo1Application : No active profile set, falling back to default profiles: default
2020-09-03 10:23:33.490 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2020-09-03 10:23:33.520 INFO 1 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2020-09-03 10:23:33.520 INFO 1 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.21]
2020-09-03 10:23:33.605 INFO 1 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2020-09-03 10:23:33.606 INFO 1 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1396 ms
2020-09-03 10:23:33.826 INFO 1 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2020-09-03 10:23:33.989 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2020-09-03 10:23:33.991 INFO 1 --- [ main] c.a.dockerdemo1.Dockerdemo1Application : Started Dockerdemo1Application in 2.319 seconds (JVM running for 2.833)
Docker 官网 Docker File指令集