本文将以 Java 为例,讲解如何通过 Dockerfile 来创建自己的开发环境。同时讲解一些 Docker 的基本命令
随着容器虚拟化被越来越多的开发人员接受,敏捷开发、 DevOps 的流行,越来越多的开发者尝试通过 Docker 提供的虚拟化方式,给开发团队建立一套具备一致性可以复用的开发、测试环境,让开发环境可以通过 Image 的形式分享给项目的所有开发成员,以简化开发环境的搭建。当然在 Docker 技术之前就已经有如 Vagrant 的开发环境分发技术,但是本文的目的是通过开发环境的搭建来让读者了解 Docker 技术的基本使用。同时要说明的是与 Vagrant 等开发环境分发技术相比, Docker 在简化 CI (持续集成) 、CD (持续交付) 有着优势,笔者将在后续的文章中进一步讲解 CI 、 CD 相关的内容。
在诸多支持跨操作系统的虚拟化软件中, Docker 很可能是最为简单易用的。尽管用 Docker 之前你仍需事先安装操作系统,但之后一切变得简单,你可以从 Docker官方或者docker.cn社区直接下载可用的模版或镜像。
如果你的系统中还没有安装好 Docker ,请参考官方文档 “ docker install ” 。这里要提醒读者的是,在 linux 安装 Docker 前请确认内核版本为3.8以上,否则将不能正常使用 Docker 。本文将以 ubuntu 14.04 为例简单讲解Docker安装
Docker 官方为 ubuntu 14.04 准备了标准的安装脚本,只要简单的下载并执行就可以了。下面给出标准的命令行
$ curl -sSL https://get.docker.com/ubuntu/ | sudo sh
在完成 Docker 安装后我们可以通过在 shell 键入 sudo docker 命令查看(注意 Docker 运行必须要 root 权限):
$ sudo docker
Usage: docker [OPTIONS] COMMAND [arg...]
-H=[unix:///var/run/docker.sock]: tcp://host:port to bind/connect to or unix://path/to/socket to use
A self-sufficient runtime for linux containers.
Commands:
attach Attach to a running container
build Build an image from a Dockerfile
commit Create a new image from a container's changes
cp Copy files/folders from a container's filesystem to the host path
diff Inspect changes on a container's filesystem
events Get real time events from the server
export Stream the contents of a container as a tar archive
history Show the history of an image
images List images
import Create a new filesystem image from the contents of a tarball
info Display system-wide information
inspect Return low-level information on a container
kill Kill a running container
load Load an image from a tar archive
login Register or log in to a Docker registry server
logout Log out from a Docker registry server
logs Fetch the logs of a container
port Lookup the public-facing port that is NAT-ed to PRIVATE_PORT
pause Pause all processes within a container
ps List containers
pull Pull an image or a repository from a Docker registry server
push Push an image or a repository to a Docker registry server
restart Restart a running container
rm Remove one or more containers
rmi Remove one or more images
run Run a command in a new container
save Save an image to a tar archive
search Search for an image on the Docker Hub
start Start a stopped container
stop Stop a running container
tag Tag an image into a repository
top Lookup the running processes of a container
unpause Unpause a paused container
version Show the Docker version information
wait Block until a container stops, then print its exit code
也可以查看安装的 Docker 版本
$sudo docker version
Client version: 1.2.0
Client API version: 1.14
Go version (client): go1.3.1
Git commit (client): fa7b24f
OS/Arch (client): linux/amd64
Server version: 1.2.0
Server API version: 1.14
Go version (server): go1.3.1
Git commit (server): fa7b24f
Docker 提供了一系列命令来管理 Docker 镜像。我们首先来看本地 Docker 库中是否已有一些镜像。
$sudo docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
使用 docker images 命令列出可用来创建 Docker 容器的本地镜像。上面命令的输出显示本地还没有任何镜像,因此我们需要先下载一镜像, Docker 镜像可以从官方下载,也可以通过第三方库下载(国内的网络情况还是推荐到docker.cn。下面将演示从官方和 docker.cn 下载 ubuntu 14.04.1
这里要说明的是 ubuntu 14.04.1 是我们产生其他镜像的一个基础镜像 (base image) 。Docker 提供了大量的基础镜像下载,当然如果你对 Docker 提供的基础镜像不满意,以可以尝试自己创建基础镜像。
$sudo docker pull ubuntu:14.04.1
Pulling repository ubuntu
9cbaf023786c: Download complete
511136ea3c5a: Download complete
97fd97495e49: Download complete
2dcbbf65536c: Download complete
6a459d727ebb: Download complete
8f321fc43180: Download complete
03db2b23cf03: Download complete
下载完成后可以使用 docker images 命令查看本地镜像
$sudo docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
ubuntu 14.04.1 9cbaf023786c 7 days ago 192.8 MB
$ sudo docker pull docker.cn/docker/ubuntu:14.04.1
Pulling repository docker.cn/docker/ubuntu
9cbaf023786c: Download complete
511136ea3c5a: Download complete
97fd97495e49: Download complete
2dcbbf65536c: Download complete
6a459d727ebb: Download complete
8f321fc43180: Download complete
03db2b23cf03: Download complete
同样从 docker.cn 下载完成后可以使用 docker images 命令查看本地镜像
$sudo docker images
docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
docker.cn/docker/ubuntu 14.04.1 9cbaf023786c 7 days ago 192.8 MB
我们下载镜像后就可以运行这个镜像了。 Docker 通过 docker run 命令可以运行一个指定的镜像。我们可以通过两种方式指定要运行的镜像
-
指定 Image Id 来运行一个镜像
docker run [IMAGE ID]
-
指定 REPOSITORY/TAG 来运行一个镜像
docker run [REPOSITORY/TAG]
下面我们以 docker.cn 的 ubuntu 14.04.1 镜像为例演示运行 Docker 镜像。以下两条命令都可以运行 Docker 的 ubuntu 镜像
- 用IMAGE ID 方式指定要运行的镜像
$sudo docker run -t -i 9cbaf023786c /bin/bash
root@8e4a0a26ae66:/#
- 用 REPOSITORY/TAG 方式指定要运行的镜像
$sudo docker run -t -i docker.cn/docker/ubuntu:14.04.1 /bin/bash
root@6d236e3af3a3:/#
在 docker run 命令中我们使用了两个参数,-t 选项让 Docker 分配一个伪终端 (pseudo-tty) 并绑定到容器的标准输入上, -i 则让容器的标准输入保持打开。在指定镜像后输入了命令 /bin/bash 启动 bash 开始 shell。
Docker 镜像 (image) 和 容器 (container) 的关系:
- 镜像 (image) 是 Docker 的模板
- 容器 (container) 是 运行一个指定镜像的实例
- Docker 实际上是通过 Linux LXC ,将镜像装入容器运行。同时一个镜像作为模板可以产生多个实例,也就是在多个容器中互不干扰的运行同一个镜像的实例。
Docker 提供了三种方式来运行镜像,下面分别来讲解这三种方式:
- 执行操作后立即退出
- 使用交互方式运行(shell)
- 运行在独立 (守护) 模式(Detached mode or Daemon mode)
我们执行 docker run 命令,来显示 Hello world
$sudo docker run docker.cn/docker/ubuntu:14.04.1 /bin/echo 'Hello world'
Hello world
上面的命令是指定使用 REPOSITORY 为 docker.cn/docker/ubuntu 、 TAG 为 14.04.1 的镜像,来运行一个容器,并且在容器容器启动后立即执行 /bin/echo 'Hello world' 命令,执行完成后这个容器就退出了,但是这个容器并没有被删除,依然存在。可以使用 docker ps 命令查看容器进程信息。
$ sudo docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7f07bb50676a docker.cn/docker/ubuntu:14.04.1 "/bin/echo 'Hello wo 13 minutes ago Exited (0) 13 minutes ago distracted_mayer
sudo docker ps -l 列出最后一个容器的信息
CONTAINER ID | IMAGE | COMMAND | CREATED | STATUS | PORTS | NAMES |
---|---|---|---|---|---|---|
7f07bb50676a | docker.cn/docker/ubuntu:14.04.1 | "/bin/echo 'Hello wo | 13 minutes ago | Exited (0) 13 minutes ago | distracted_mayer |
下面给出各部分所代表的含义:
-
CONTAINER ID
7f07bb50676a
: CONTAINER ID 是我们管理容器的重要标志,我们可以通过 CONTAINER ID 来停止、删除、重新启动一个容器 -
IMAGE
docker.cn/docker/ubuntu:14.04.1
:启动镜像标识 -
COMMAND
"/bin/echo 'Hello wo
:容器运行的命令 -
CREATED
13 minutes ago
:容器创建的时间 -
STATUS
Exited (0) 13 minutes ago
:容器运行状态和时间 -
PORTS
空
:容器对外暴露的端口 -
NAMES
distracted_mayer
:容器使用的名字
从上面的 STATUS 列,可以看出刚才显示 "Hello World" 的容器已经退出。
Docker 允许我们在启动一个容器后保持标准输入一直打开 ( -i
参数 ),并通过伪终端 (-t
参数) 来进行交互,
$sudo docker run -t -i docker.cn/docker/ubuntu:14.04.1 /bin/bash
root@32d83047546f:/#
上面的命令启动了 docker.cn/docker/ubuntu:14.04.1 镜像的容器实例,并且运行了bash,因为容器一直保持标准输入打开( -i
参数 ),并提供了伪终端( -t
参数),所以我们就可以通过 bash 操作容器了。
我们可以简单的在容器的 bash 中输入 ll
来显示容器内当前目录的文件信息。
root@32d83047546f:/# ll
total 72
drwxr-xr-x 47 root root 4096 Oct 22 06:20 ./
drwxr-xr-x 47 root root 4096 Oct 22 06:20 ../
-rwxr-xr-x 1 root root 0 Oct 22 06:20 .dockerenv*
-rwxr-xr-x 1 root root 0 Oct 22 06:20 .dockerinit*
drwxr-xr-x 2 root root 4096 Oct 13 03:38 bin/
drwxr-xr-x 2 root root 4096 Apr 10 2014 boot/
drwxr-xr-x 4 root root 360 Oct 22 06:20 dev/
drwxr-xr-x 64 root root 4096 Oct 22 06:20 etc/
drwxr-xr-x 2 root root 4096 Apr 10 2014 home/
drwxr-xr-x 12 root root 4096 Oct 13 03:37 lib/
drwxr-xr-x 2 root root 4096 Oct 13 03:36 lib64/
drwxr-xr-x 2 root root 4096 Oct 13 03:34 media/
drwxr-xr-x 2 root root 4096 Apr 10 2014 mnt/
drwxr-xr-x 2 root root 4096 Oct 13 03:34 opt/
dr-xr-xr-x 275 root root 0 Oct 22 06:20 proc/
drwx------ 2 root root 4096 Oct 13 03:38 root/
drwxr-xr-x 7 root root 4096 Oct 13 03:37 run/
drwxr-xr-x 2 root root 4096 Oct 13 21:19 sbin/
drwxr-xr-x 2 root root 4096 Oct 13 03:34 srv/
dr-xr-xr-x 13 root root 0 Oct 22 06:20 sys/
drwxrwxrwt 2 root root 4096 Oct 13 21:20 tmp/
drwxr-xr-x 11 root root 4096 Oct 13 03:34 usr/
drwxr-xr-x 15 root root 4096 Oct 13 03:38 var/
root@32d83047546f:/#
下面我们在刚刚启动的 ubuntu 容器中进行一下系统更新
root@32d83047546f:/# apt-get update
Ign http://archive.ubuntu.com trusty InRelease
Ign http://archive.ubuntu.com trusty-updates InRelease
Ign http://archive.ubuntu.com trusty-security InRelease
Ign http://archive.ubuntu.com trusty-proposed InRelease
Get:1 http://archive.ubuntu.com trusty Release.gpg [933 B]
Get:2 http://archive.ubuntu.com trusty-updates Release.gpg [933 B]
Get:3 http://archive.ubuntu.com trusty-security Release.gpg [933 B]
Get:4 http://archive.ubuntu.com trusty-proposed Release.gpg [933 B]
Get:5 http://archive.ubuntu.com trusty Release [58.5 kB]
Get:6 http://archive.ubuntu.com trusty-updates Release [59.7 kB]
Get:7 http://archive.ubuntu.com trusty-security Release [59.7 kB]
...
root@32d83047546f:/# apt-get upgrade
Reading package lists... Done
Building dependency tree
Reading state information... Done
Calculating upgrade... Done
The following packages will be upgraded:
libssl1.0.0 tzdata
2 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
Need to get 1003 kB of archives.
After this operation, 35.8 kB disk space will be freed.
...
root@32d83047546f:/#
现在已经更新了容器内的系统,下一步我们来保存这个更新的结果,也就是将更新后的容器保存为一个新的镜像。首先我们先退出这个容器:
root@32d83047546f:/# exit
exit
root@dockerdev:~#
我们可以使用 exit
命令,或者快捷键 Ctrl+d
来退出 shell,退出 shell 后容器因为没有要执行的操作了,也就退出了。现在我们通过 docker ps -l
命令来查看最后一个运行的容器,也就是我们刚刚在更新后退出的 ubuntu 容器。
$sudo docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
09b218b78ea3 docker.cn/docker/ubuntu:14.04.1 "/bin/bash" 15 minutes ago Exited (0) 15 minutes ago sick_einstein
CONTAINER ID | IMAGE | COMMAND | CREATED | STATUS | PORTS | NAMES |
---|---|---|---|---|---|---|
09b218b78ea3 | docker.cn/docker/ubuntu:14.04.1 | "/bin/bash" | 15 minutes ago | Exited (0) 15 minutes ago | sick_einstein |
这就是我们刚刚更新后退出的容器信息,我们接下来使用 dokcer commit 命令将这个容器设置为一个行的镜像
$sudo docker commit -m "ubuntu update & upgrade" -a "DockerDev" 09b218b78ea3 dockerdev/ubuntu:upbase
0e109fb78933beba666ca2dff991e1913475549a2cb498deeed4b84710ab7919
$sudo docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
dockerdev/ubuntu upbase 0e109fb78933 2 minutes ago 192.8 MB
docker.cn/docker/ubuntu 14.04.1 9cbaf023786c 8 days ago 192.8 MB
docker commit 命令 我们使用了两个参数:
-m "ubuntu update & upgrade"
填写一些要提交为镜像的说明信息-a "DockerDev"
提交人的信息
09b218b78ea3
是我们通过 docker ps -l
命令查询到的容器 ID
dockerdev/ubuntu:upbase
是我们给要新提交创建镜像的 REPOSITORY:TAG
执行 docker images
命令我们可以看到,我们已经通过 docker commit
命令创建了一个新的镜像
新产生镜像的基本信息如下
REPOSITORY:dockerdev/ubuntu
TAG:upbase
IMAGE ID:0e109fb78933
下面,我们来让 Docker 以独立 (守护) 模式 (Detached mode or Daemon mode) 来运行容器。
$sudo docker run -d dockerdev/ubuntu:upbase /bin/sh -c "while true; do echo hello world; sleep 1; done"
56919c8c390e7cc9d60cb2405348dc07ed7f99ad555d2c1861ff22f8763685f7
$sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
56919c8c390e dockerdev/ubuntu:upbase "/bin/sh -c 'while t 16 seconds ago Up 15 seconds silly_lumiere
上面我们使用的 -d
参数来指定将刚刚创建的镜像 dockerdev/ubuntu:upbase 以独立 (守护) 模式运行,只用我们通过让这个运行的容器每隔1秒就输出一个 "hello world"
可以通过docker logs 命令来查看一个指定容器的日志输出 在这里,我们将能看到输出很多行 "hello world"
$sudo docker logs 56919c8c390e
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
56919c8c390e
是我们以独立 (守护) 模式运行的容器ID 。
接下来我们可以停止这个以独立 (守护) 模式运行的容器,毕竟一直输出 "hello world" 没有什么实际意义,我们可以通过 docker stop 命令来停止一个正在运行的容器。
$sudo docker stop 56919c8c390e
56919c8c390e
$sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
$sudo docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
56919c8c390e dockerdev/ubuntu:upbase "/bin/sh -c 'while t 6 minutes ago Exited (-1) 5 minutes ago silly_lumiere
在 stop ID 为 56919c8c390e
的容器后,我们使用 docker ps 命令查看容器进程已经为空,说明容器已经停止。我们也可以使用 docker ps -l
命令显示最后一个运行的容器,我们可以看到我们停止的容器现在的状态为 Exited (-1)
。
刚才我们已经构建了一个已经更新完毕的 ubunt 14.04.1 为基础的镜像,下面我们将以此镜像为基础构建 Java+Tomcat+Redis 开发环境。
我们将以 dockerdev/ubuntu:upbase 镜像为基础,通过 Dockerfile 构建 openjdk 和 redis 镜像,再以 openjdk 为基础构建 tomcat 镜像。
Dockerfile 类似 shell 脚本,我们可以将构建镜像的一系列命令写进 Dockerfile ,批量执行并产生新的镜像。
在前面的内容中,我们通过交互方式更新了 ubuntu ,并且通过 docker commit 保存为一个新的镜像,但是这并不是Docker推荐的镜像构建方式。在接下来的内容中,我们将学习如何通过编写Dockerfile脚本来构建和维护镜像。
首先我们要准备一下目录结构和文件
dockerdev
├── dockerdev.openjdk
│ └── Dockerfile
├── dockerdev.redis
│ └── Dockerfile
└── dockerdev.tomcat
└── Dockerfile
我们以 dockerdev.openjdk 的 Dockerfile 为例来讲解 Dockerfile 基础
我们先看一下用来创建 openjdk 的 Dockerfile 内容,然后我们对其中指令进行讲解:
#
#OpenJDK Java 7 JDK Dockerfile
#
#docker.cn
#
#Pull base image.
FROM dockerdev/ubuntu:upbase
MAINTAINER fivestarsky
#Install Java.
RUN \
apt-get update && \
apt-get install -y openjdk-7-jdk && \
rm -rf /var/lib/apt/lists/*
#Define working directory.
WORKDIR /
#Define commonly used JAVA_HOME variable
ENV JAVA_HOME /usr/lib/jvm/java-7-openjdk-amd64
#Define default command.
CMD ["bash"]
通过FROM指令,docker编译程序能够知道在哪个基础镜像执行来进行编译。所有的Dockerfile都必须以FROM指令开始。 Docker 会首先查找本地镜像库是否有指定的镜像,如果没有将尝试下载指定的镜像。
MAINTAINER,用来标明这个镜像的维护者信息。
Run 指令用来在 Docker 的编译环境中运行指定命令。上面这条指令会在编译环境运行 /bin/sh -c "apt-get update && apt-get -y install ..."。
每条 RUN 指令将在当前镜像基础上执行指定命令,并提交为新的镜像。当命令较长时可以使用 \ 来换行。
在使用 Run 指令的时候需要注意每一个 Run 指令都会让 Docker 产生一个新层(layer),而 Docker 规定一个镜像最多有127层。所以我们应该尽量将多个操作合并为一个 RUN 指令
RUN 指令还有另外一种格式: RUN ["程序名", "参数1", "参数2"] 这种格式运行程序,可以免除运行 /bin/sh 的消耗。这种格式使用 Json 格式将程序名与所需参数组成一个字符串数组,所以如果参数中有引号等特殊字符,需要进行转义。
WORKDIR 指令用于设置执行 RUN 指令、 CMD 指令和 ENTRYPOINT 指令执行时的工作目录。在 Dockerfile 中可以多次设置 WORKDIR ,在每次设置之后的命令将使用新的路径。
ENV 指令用来指定在执行 docker run 命令运行镜像时,自动设置的环境变量。这些环境变量可以通过 docker run 命令的 -evn 参数来进行修改。
注意:ENV 指令指定的环境变量在运行创建的镜像时依然有效
CMD指令通常是整个 Dockerfile 脚本的最后一条指令。当 Dockerfile 已经完成了所有环境的安装与配置,通过 CMD 指令来指示 docker run 命令运行镜像时要执行的命令。上面的例子里,在完成所有工作后,Dockerfile编译脚本将启动 bash 。
ENTRYPOINT 指令和前面介绍过的 CMD 一样,用于标明一个镜像作为容器运行时,最后要执行的程序或命令。
这两个指令有相同只处,也有区别。通过两个指令的配合使用可以配置出不同的效果。
ENTRYPOINT 指令有两种格式,CMD指令有三种格式:
ENTRYPOINT ["程序名", "参数1", "参数2"]
ENTRYPOINT 命令 参数1 参数2
CMD ["程序名", "参数1", "参数2"]
CMD 命令 参数1 参数2
CMD 参数1 参数2
ENTRYPOINT 是容器运行程序的入口。也就是说,在 docker run 命令中指定的命令都将作为参数提供给 ENTRYPOINT 指定的程序。 同样,上面列举的 CMD 指令格式的后面两种格式也将作为参数提供给 ENTRYPOINT 指定的程序。
默认的 ENTRYPOINT 是/ bin/sh -c 。你可以根据实际需要任意设置。但是如果在一个 Dockerfile 中出现了多个 ENTRYPOINT 指令,那么,只有最后一个 ENTRYPOINT 指令是起效的。
一种常用的设置是将命令与必要参数设置到 ENTRYPOINT 中,而运行时只提供其他选项。例如:你有一个 MySQL 的客户端程序运行在容器中,而客户端所需要的主机地址、用户名和密码你不希望每次都输入,你就可以将 ENTRYPOINT 设置成:ENTRYPOINT mysql -u <用户名> -p <密码> -h <主机名>
。而运行这个镜像时,只需要指定数据库名。
#####注意:CMD 和 ENTRYPOINT 是在每次运行镜像的时候都执行的
下面统一给出我们需要的所有 Dockerfile:
dockerdev
└── dockerdev.openjdk
└── Dockerfile
#
#OpenJDK Java 7 JDK Dockerfile
#
#docker.cn
#
#Pull base image.
FROM dockerdev/ubuntu:upbase
MAINTAINER fivestarsky
#Install Java.
RUN \
apt-get update && \
apt-get install -y openjdk-7-jdk && \
rm -rf /var/lib/apt/lists/*
#Define working directory.
WORKDIR /
#Define commonly used JAVA_HOME variable
ENV JAVA_HOME /usr/lib/jvm/java-7-openjdk-amd64
#Define default command.
CMD ["bash"]
dockerdev
└── dockerdev.redis
└── Dockerfile
#
#Redis Dockerfile
#
#docker.cn
#
#Pull base image.
FROM dockerdev/ubuntu:upbase
MAINTAINER fivestarsky
#Install Redis.
RUN \
cd /tmp && \
apt-get update && \
apt-get install -y wget g++ make && \
wget http://download.redis.io/redis-stable.tar.gz && \
tar xvzf redis-stable.tar.gz && \
cd redis-stable && \
make && \
make install && \
cp -f src/redis-sentinel /usr/local/bin && \
mkdir -p /etc/redis && \
cp -f *.conf /etc/redis && \
rm -rf /tmp/redis-stable* && \
sed -i 's/^\(bind .*\)$/# \1/' /etc/redis/redis.conf && \
sed -i 's/^\(daemonize .*\)$/# \1/' /etc/redis/redis.conf && \
sed -i 's/^\(dir .*\)$/# \1\ndir \/data/' /etc/redis/redis.conf && \
sed -i 's/^\(logfile .*\)$/# \1/' /etc/redis/redis.conf
#Define mountable directories.
VOLUME ["/data"]
#Define working directory.
WORKDIR /data
#Define default command.
CMD ["redis-server", "/etc/redis/redis.conf"]
#Expose ports.
EXPOSE 6379
redis Dockerfile 出现了一些新的指令我们进行简单解释:
- VOLUME 指令
格式为 VOLUME ["/data"]。
创建一个可以从本地主机或其他容器挂载的挂载点,一般用来存放数据库和需要保持的数据等。
- EXPOSE 指令
格式为 EXPOSE [...]。
告诉 Docker 服务端容器暴露的端口号,供互联系统使用。在启动容器时需要通过 -P,Docker 主机会自动分配一个端口转发到指定的端口。
dockerdev
└── dockerdev.tomcat
└── Dockerfile
#
#Tomcat7 Dockerfile
#
#docker.cn
#
FROM openjdk
MAINTAINER fivestarsky
RUN apt-get update && \
apt-get install -yq --no-install-recommends wget pwgen ca-certificates && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
ENV TOMCAT_MAJOR_VERSION 7
ENV TOMCAT_MINOR_VERSION 7.0.56
ENV CATALINA_HOME /tomcat
#INSTALL TOMCAT
RUN wget -q https://archive.apache.org/dist/tomcat/tomcat-${TOMCAT_MAJOR_VERSION}/v${TOMCAT_MINOR_VERSION}/bin/apache-tomcat-${TOMCAT_MINOR_VERSION}.tar.gz && \
wget -qO- https://archive.apache.org/dist/tomcat/tomcat-${TOMCAT_MAJOR_VERSION}/v${TOMCAT_MINOR_VERSION}/bin/apache-tomcat-${TOMCAT_MINOR_VERSION}.tar.gz.md5 | md5sum -c - && \
tar zxf apache-tomcat-*.tar.gz && \
rm apache-tomcat-*.tar.gz && \
mv apache-tomcat* tomcat
EXPOSE 8080
CMD /tomcat/bin/catalina.sh run
在编写完这三个Dockerfile后 我们就可以将 Dockerfile 编译为镜像了,我们将逐一编译三个 Dockerfile 并讲解编译的依赖关系:
编译 dockerdev.openjdk/Dockerfile
进入 dockerdev/dockerdev.openjdk 目录,并执行命令
dockerdev/dockerdev.openjdk# sudo docker build -t openjdk .
Sending build context to Docker daemon 2.56 kB
Sending build context to Docker daemon
Step 0 : FROM dockerdev/ubuntu:upbase
---> 0e109fb78933
Step 1 : MAINTAINER fivestarsky
---> Running in 1cf07d9a953e
---> 7b110000a941
Removing intermediate container 1cf07d9a953e
Step 2 : RUN apt-get update && apt-get install -y openjdk-7-jdk && rm -rf /var/lib/apt/lists/*
---> Running in 41ca78e318c5
Ign http://archive.ubuntu.com trusty InRelease
Ign http://archive.ubuntu.com trusty-updates InRelease
Ign http://archive.ubuntu.com trusty-security InRelease
Ign http://archive.ubuntu.com trusty-proposed InRelease
...
done.
done.
Processing triggers for sgml-base (1.26+nmu4ubuntu1) ...
---> 53a0764665b1
Removing intermediate container 41ca78e318c5
Step 3 : WORKDIR /
---> Running in a3366ac9972c
---> c3d1332cb5c6
Removing intermediate container a3366ac9972c
Step 4 : ENV JAVA_HOME /usr/lib/jvm/java-7-openjdk-amd64
---> Running in 48fe1533fcd4
---> 4474ea8b1ab8
Removing intermediate container 48fe1533fcd4
Step 5 : CMD ["bash"]
---> Running in 584cb3f262de
---> 32269ea6368f
Removing intermediate container 584cb3f262de
Successfully built 32269ea6368f
编译完成后我们可以看到 Successfully built 提示。接下来我们可以执行 docker images 命令查看编译后产生的镜像。
$sudo docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
openjdk latest 32269ea6368f About a minute ago 578.6 MB
dockerdev/ubuntu upbase 0e109fb78933 3 hours ago 192.8 MB
docker.cn/docker/ubuntu 14.04.1 9cbaf023786c 8 days ago 192.8 MB
这里需要注意:我们在执行编译时 加入的
-t openjdk
参数 是为编译产生的镜像指定一个 REPOSITORY ,如果不指定 TAG 则默认为 latest, 指定 TAG 可以表示为:-t openjdk:tagname
。
接下来我们编译 tomcat
编译 dockerdev.tomcat/Dockerfile
进入 dockerdev/dockerdev.tomcat 目录,并执行命令
dockerdev/dockerdev.tomcat# sodu docker build -t opentomcat:latest .
Sending build context to Docker daemon 3.072 kB
Sending build context to Docker daemon
Step 0 : FROM openjdk
---> 32269ea6368f
Step 1 : MAINTAINER fivestarsky
---> Running in 796db53b96d7
---> 1b68febc5c4f
Removing intermediate container 796db53b96d7
Step 2 : RUN apt-get update && apt-get install -yq --no-install-recommends wget pwgen ca-certificates && apt-get clean && rm -rf /var/lib/apt/lists/*
---> Running in 65883eb7ff36
...
Removing intermediate container 65883eb7ff36
Step 3 : ENV TOMCAT_MAJOR_VERSION 7
---> Running in 793ce0acb114
---> a48458c0363f
Removing intermediate container 793ce0acb114
Step 4 : ENV TOMCAT_MINOR_VERSION 7.0.56
---> Running in 2dd8a1bf81d1
---> 602f00a98040
Removing intermediate container 2dd8a1bf81d1
Step 5 : ENV CATALINA_HOME /tomcat
---> Running in 641c4c1d402e
---> c796492000b2
Removing intermediate container 641c4c1d402e
Step 6 : RUN wget -q https://archive.apache.org/dist/tomcat/tomcat-${TOMCAT_MAJOR_VERSION}/v${TOMCAT_MINOR_VERSION}/bin/apache-tomcat-${TOMCAT_MINOR_VERSION}.tar.gz && wget -qO- https://archive.apache.org/dist/tomcat/tomcat-${TOMCAT_MAJOR_VERSION}/v${TOMCAT_MINOR_VERSION}/bin/apache-tomcat-${TOMCAT_MINOR_VERSION}.tar.gz.md5 | md5sum -c - && tar zxf apache-tomcat-*.tar.gz && rm apache-tomcat-*.tar.gz && mv apache-tomcat* tomcat
---> Running in 370717c866c3
apache-tomcat-7.0.56.tar.gz: OK
---> cce75f69363b
Removing intermediate container 370717c866c3
Step 7 : EXPOSE 8080
---> Running in a324157603cf
---> a824f34d2dd9
Removing intermediate container a324157603cf
Step 8 : CMD /tomcat/bin/catalina.sh run
---> Running in 2446926e243a
---> a2246837501f
Removing intermediate container 2446926e243a
Successfully built a2246837501f
编译完成后我们可以看到 Successfully built 提示。接下来我们可以执行 docker images 命令查看编译后产生的镜像。
$sudo docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
opentomcat latest a2246837501f 13 minutes ago 595.9 MB
openjdk latest 32269ea6368f 24 minutes ago 578.6 MB
dockerdev/ubuntu upbase 0e109fb78933 3 hours ago 192.8 MB
docker.cn/docker/ubuntu 14.04.1 9cbaf023786c 8 days ago 192.8 MB
这里需要注意: tomcat 的 Dockerfile 我们在开头指定的 FROM openjdk 依赖于前面编译的 openjdk:latest 镜像,如果我们前面没有正常产生openjdk:latest 镜像 Docker 会尝试到网络仓库去下载,如果也没有找到将会报错! 也就是说 tomcat 镜像依赖于 openjdk 镜像才能进行编译。
接下来我们编译最后一个 redis Dockerfile 。
进入 dockerdev/dockerdev.redis 目录,并执行命令。
dockerdev/dockerdev.redis#sudo docker build -t openredis:latest .
Sending build context to Docker daemon 3.072 kB
Sending build context to Docker daemon
Step 0 : FROM dockerdev/ubuntu:upbase
---> 0e109fb78933
Step 1 : MAINTAINER fivestarsky
---> Using cache
---> 7b110000a941
Step 2 : RUN cd /tmp && apt-get update && apt-get install -y wget && apt-get install -y g++ make && wget http://download.redis.io/redis-stable.tar.gz && tar xvzf redis-stable.tar.gz && cd redis-stable && make && make install && cp -f src/redis-sentinel /usr/local/bin && mkdir -p /etc/redis && cp -f *.conf /etc/redis && rm -rf /tmp/redis-stable* && sed -i 's/^\(bind .*\)$/# \1/' /etc/redis/redis.conf && sed -i 's/^\(daemonize .*\)$/# \1/' /etc/redis/redis.conf && sed -i 's/^\(dir .*\)$/# \1\ndir \/data/' /etc/redis/redis.conf && sed -i 's/^\(logfile .*\)$/# \1/' /etc/redis/redis.conf
---> Running in e3f9ece34e10
Ign http://archive.ubuntu.com trusty InRelease
Ign http://archive.ubuntu.com trusty-updates InRelease
Ign http://archive.ubuntu.com trusty-security InRelease
Ign http://archive.ubuntu.com trusty-proposed InRelease
Get:1 http://archive.ubuntu.com trusty Release.gpg [933 B]
Get:2 http://archive.ubuntu.com trusty-updates Release.gpg [933 B]
Get:3 http://archive.ubuntu.com trusty-security Release.gpg [933 B]
Get:4 http://archive.ubuntu.com trusty-proposed Release.gpg [933 B]
Get:5 http://archive.ubuntu.com trusty Release [58.5 kB]
Get:6 http://archive.ubuntu.com trusty-updates Release [59.7 kB]
Get:7 http://archive.ubuntu.com trusty-security Release [59.7 kB]
Get:8 http://archive.ubuntu.com trusty-proposed Release [110 kB]
...
Hint: It's a good idea to run 'make test' ;)
make[1]: Leaving directory `/tmp/redis-stable/src'
cd src && make install
make[1]: Entering directory `/tmp/redis-stable/src'
Hint: It's a good idea to run 'make test' ;)
INSTALL install
INSTALL install
INSTALL install
INSTALL install
INSTALL install
make[1]: Leaving directory `/tmp/redis-stable/src'
---> edf2f52bf379
Removing intermediate container e3f9ece34e10
Step 3 : VOLUME ["/data"]
---> Running in e4829536357c
---> 7d05ddb6c52d
Removing intermediate container e4829536357c
Step 4 : WORKDIR /data
---> Running in 7d9b1d23f0d4
---> 2f456b8b46e3
Removing intermediate container 7d9b1d23f0d4
Step 5 : CMD ["redis-server", "/etc/redis/redis.conf"]
---> Running in 5428d46542a8
---> a6d5744ad999
Removing intermediate container 5428d46542a8
Step 6 : EXPOSE 6379
---> Running in 6b26b08ff26f
---> 0a41117a14ef
Removing intermediate container 6b26b08ff26f
Successfully built 0a41117a14ef
编译完成后我们可以看到 Successfully built 提示。接下来我们可以执行 docker images 命令查看编译后产生的镜像。
$sudo docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
openredis latest 0a41117a14ef About a minute ago 331.3 MB
opentomcat latest a2246837501f 34 minutes ago 595.9 MB
openjdk latest 32269ea6368f 46 minutes ago 578.6 MB
dockerdev/ubuntu upbase 0e109fb78933 4 hours ago 192.8 MB
docker.cn/docker/ubuntu 14.04.1 9cbaf023786c 8 days ago 192.8 MB
至此我们将 java + tomcat + redis 开发环境所需的镜像全部编译完成。
首先,我们来讲解一下我们如何配置并运行容器:我们将使用 opentomcat 和 openredis 镜像运行两个独立 (守护) 模式的容器。在 java 的 war 包中我们将参数 put 到 redis 进行存储并遍历显示。相关 war 包和 java 代码请从底部链接下载。
- opentomcat 容器挂载 host 宿主机的指定目录作为 webapps 目录,方便从宿主机直接部署 war 包
- opentomcat 容器对外开放 8080 端口 提供 web 访问
- opentomcat 通过容器间 link 方式访问 openredis
在 dockerdev/dockerdev.tomcat/ 目录下建立 webapps 子目录,并且将下载的 DemoWebDev.war 文件复制到 webapps 子目录.
$sudo docker run -d --name redis_server openredis
0827f4ba71bf776f1a9127e5880c49b3d2e55760c7e2ce0b604aff59752ad030
$sudo docker run -d -p 8080:8080 --link redis_server:redis_server -v /root/dockerdev/dockerdev.tomcat/webapps:/tomcat/webapps opentomcat
4d1bf5d6621424da0f9be45193b42221bd1c09ac0b906ab930de8ecd44c6f3de
下面我们来解释相关参数含义
- docker run -d
--name redis_server
openredis:为运行的容器设置一个名字
- docker run -d -p 8080:8080
--link redis_server:redis_server
... opentomcat: 要链接的容器名字(对应--name redis_server
):在 opentomcat 容器内部链接容器的名字
- docker run -d
-p 8080:8080
--link redis_server ... opentomcat: opentomcat 容器对外开放 8080 端口,8080映射宿主机端口
:8080容器内部端口
- docker run -d ...
-v /root/dockerdev/dockerdev.tomcat/webapps:/tomcat/webapps
opentomcat : 挂载宿主机/root/dockerdev/dockerdev.tomcat/webapps到容器的/tomcat/webapps。也就是我们可以在宿主机部署 war 包,可以被容器立刻发现并使用
在启动两个容器后我们可以查看 dockerdev/dockerdev.tomcat/webapps 目录 会发现 DemoWebDev.war 已经解压为 DemoWebDev 目录,这就是因为我们将宿主机的一个卷 (/root/dockerdev/dockerdev.tomcat/webapps) 挂载到了容器里(/tomcat/webapps),容器可以实时发现挂载卷的内容改变。
接下来我们可以测试一下我们的 DemoWebDev 在浏览器输入: http://host:8080/DemoWebDev/TestRedisPush?pushvalue=demo, host 为宿主机的ip地址、pushvalue 是我们要存储到 redis 的数据
push:demo OK!
now redis list values: [demo]
有的时候我们需要进入运行在独立 (守护) 模式下的容器,并通过shell查看容器内部信心, Docker 提供了多种方式进入容器,我们在这里介绍比较常用的一种 nsenter 。
提示:
- util-linux 是一个开放源码的软件包,是一个对任何Linux系统的基本工具套件。含有一些标准 UNIX 工具,如 login。
- util-linux 软件包包含许多工具。其中比较重要的是加载、卸载、格式化、分区和管理硬盘驱动器,打开 tty 端口和得到内核消息。
首先我们需要安装 nsenter 来进入 Docker 容器,nsenter 在 util-linux版本2.23开始提供很不幸,Ubuntu 14.04 仍然使用的是 util-linux 版本 2.20 ,所以我们需要安装新版的 util-linux。
从 util-linux 版本 2.23 开始,nsenter工具就包含在其中。它用来访问另一个进程的名字空间。 nsenter 要正常工作需要有 root 权限。安装比较新版本的 util-linux(2.24) 版,请按照以下步骤:
wget https://www.kernel.org/pub/linux/utils/util-linux/v2.24/util-linux-2.24.tar.gz; tar xzvf util-linux-2.24.tar.gz
cd util-linux-2.24
./configure --without-ncurses && make nsenter
sudo cp nsenter /usr/local/bin
安装完成后可以通过 nsenter -v 来查看安装是否成功以及 nsenter 版本:
$sudo nsenter -V
nsenter from util-linux 2.24
我们可以通过 docker ps 命令查看正在运行的容器,并得到容器 ID,然后查询容器 ID 所在的进程 ID,最后使用 nsenter 通过进程ID 进入 docker 容器。
为了连接到容器,我们需要找到容器的第一个进程的 PID,可以通过下面的命令获取。
PID=$(docker inspect --format "{{ .State.Pid }}" <container id>)
通过这个 PID,就可以连接到这个容器:
$ nsenter --target $PID --mount --uts --ipc --net --pid
这里我们以进入redis 容器为例:
$sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4d1bf5d66214 opentomcat:latest "/bin/sh -c '/tomcat 27 minutes ago Up 27 minutes 0.0.0.0:8080->8080/tcp goofy_einstein
0827f4ba71bf openredis:latest "redis-server /etc/r 28 minutes ago Up 28 minutes 6379/tcp goofy_einstein/redis_server,redis_server
$sudo PID=$(docker inspect --format "{{ .State.Pid }}" 0827f4ba71bf)
$sudo nsenter --target $PID --mount --uts --ipc --net --pid
groups: cannot find name for group ID 122
groups: cannot find name for group ID 123
root@0827f4ba71bf:/#
我们应该能看出来,nsenter 的使用步骤还是比较麻烦的,实际上已经有人替我们封装好了脚本 方便我们访问 Docker 容器,下面我们来下载这个脚本并使用这个脚本方便的访问 Docker 容器。
采用 .bashrc_docker 简化 nsenter (作者的 Github)。
wget -P ~ https://github.com/yeasy/docker_practice/raw/master/_local/.bashrc_docker;
echo "[ -f ~/.bashrc_docker ] && . ~/.bashrc_docker" >> ~/.bashrc; source ~/.bashrc
脚本安装后我们可以使用如下格式来进入 Docker 容器:
直接进入容器
$ sudo docker-enter <container id>
在容器内执行操作,并显示
$ sudo docker-enter <container id> ls
下面我们依然以进入 redis-server 容器为例演示脚本的使用:
$sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4d1bf5d66214 opentomcat:latest "/bin/sh -c '/tomcat 40 minutes ago Up 40 minutes 0.0.0.0:8080->8080/tcp goofy_einstein
0827f4ba71bf openredis:latest "redis-server /etc/r 41 minutes ago Up 41 minutes 6379/tcp goofy_einstein/redis_server,redis_server
$sudo docker-enter 0827f4ba71bf
dirname: invalid option -- 'b'
Try 'dirname --help' for more information.
root@0827f4ba71bf:~#
root@0827f4ba71bf:~#ll
total 24
drwx------ 2 root root 4096 Oct 23 01:28 ./
drwxr-xr-x 69 root root 4096 Oct 23 01:28 ../
-rw------- 1 root root 21 Oct 23 01:28 .bash_history
-rw-r--r-- 1 root root 3106 Feb 20 2014 .bashrc
-rw-r--r-- 1 root root 140 Feb 20 2014 .profile
-rw-r--r-- 1 root root 12 Oct 23 01:28 .rediscli_history
root@0827f4ba71bf:~#redis-cli
127.0.0.1:6379> keys *
1) "TestList"
127.0.0.1:6379> exit
root@0827f4ba71bf:~# exit
logout
从上面的演示可以看出,我们可以进入 redis-server 容器,并且调用容器内部的 redis-cli 查询所有的 redis keys 。
到此为止,我们已经简单的学习了Docker 如何搭建 Java+tomcat+redis 开发环境的搭建,对 Docker 有了初步的认识。