近来发现docker越来越受欢迎了,而且看起来,docker的热度还远没有到达顶峰,或许在这个云服务时代的大背景下,docker会是另一个转折点。这里暂且将这几天看到的一些小知识点整理下,方便自己后面复习吧。
安装docker
docker虽然是跨平台的,但是很明显在linux上工作起来要比在Windows上方便的多,所以我就在自己的云服务器上装一个好了。
- 包管理器:
$sudo apt-get install docker-ce
, 关于新(docker-ce
),旧(docker
,docker-io
)版本,这里就不多说了,个人觉得版本还是LTS的最新版比较好。 - shell方式安装:
$ wget -qO- https://get.docker.com/ | sh
到这里基本上,环境准备工作就算是结束了,然后就可以开始docker之旅了。
概念
在正式使用docker之前,还是得先了解下关于docker的一些关键字。
镜像:简单来类比思考,docker的镜像就像是一个ISO文件,被docker引擎加载之后就相当于是一个系统。但是实际上一个docker镜像却远远没有我们平时看到的系统镜像那么大,这是因为docker对镜像的分层构建(底层是Union FS技术)实现的。因此,一个docker其实就是一系列文件的集合,被一层层的构建起来的。具体的例子也很好表述:我们在pull下来一个系统镜像之后,肯定是为了定制来满足自己的开发需求的,期间会在安装一些软件或者搭建环境,这就相当于在原来的镜像基础上又包装了一层文件系统。我们所安装的软件会持久化到当前的镜像中,如果此镜像被构建成新的镜像,别人pull了之后就可以直接使用。因此每一层的构建都最好不要把不必要的“文件”放进去,以免镜像越来越大。
容器:类似于程序和进程,镜像是一个文件集合,而容器就是实际运行起来的可以被创建,开启,终止,暂停,删除的实体。或者这样形容,镜像和容器其实就是类和示例的翻版。docker的容器实际上是一个独立的进程,独立于宿主的操作系统下的实际“跑”起来的镜像。正是由于它与宿主系统的隔离特性,因此docker会很安全。即使是整个服务崩掉或者被攻击,也只是docker容器本身的系统宕掉。不会对宿主机器造成过大的影响。
-
仓库:顾名思义,仓库是一个保存某种数据的存在。类似于git仓库,这里当然是保存docker的镜像了。镜像通过添加的标签的方式来保存,具体的格式为:
<仓库名>:
。如果tag名没有具体描述,则使用latest
作为缺省名。类似于git仓库,docker的仓库通常也会是两段式路径的方式存在。举例如下:- git:
username/repo-name
- docker:
username/software
, 第二个参数具体是什么名称也视具体情况而定。
关于默认仓库,国内外有很多网站都提供技术支持,按自己的需求去搜索即可。
- git:
镜像id和容器id:镜像id和容器id就像是相对于二者的一个唯一的标识符,一般是一个12位长度的字符串,配合下面的命令可以实现一系列的操作。
总的来看,现在很多软件以及体系的结构都是类似的,docker也不例外。在学习的过程中,不妨将docker与自己已经了解的一些概念做下类比,这样不仅有助于了解软件的生态体系,也方便后序的理解。
常用命令
- 在命令行输入
docker --help
会看到相关的命令参数, - 输入
docker subcommand --help
来查看具体的子命令的使用帮助。
举例如下:
guo@Server218:~$ docker pull --help
Usage: docker pull [OPTIONS] NAME[:TAG|@DIGEST]
Pull an image or a repository from a registry
Options:
-a, --all-tags Download all tagged images in the repository
--disable-content-trust Skip image verification (default true)
--help Print usage
这样就可以查看具体的某一个命令的使用方式了,这对于后面熟练的使用docker是比较有帮助的。接下来,我就记录下我这两天用到的一些简单的命令的使用,很基础,但是也很重要。
docker pull
pull 是一个动词,中文意思为“拉”,这里可以理解为将一个镜像从远端仓库(具体也可以自己配置,默认是官方镜像仓库)下载到本地。docker在pull之前会检查本地是否已经存在了是否下载过此镜像,如果没有本地确实没有下载过,才会到远端仓库进行下载。
guo@Server218:~$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntunginx latest ca3292d807d3 41 hours ago 177 MB
redis 3.2 e97b1f10d81a 2 weeks ago 99.7 MB
ubuntu latest 452a96d81c30 3 weeks ago 79.6 MB
hello-world latest e38bc07ac18e 5 weeks ago 1.85 kB
guo@Server218:~$ docker pull debian
Using default tag: latest
latest: Pulling from library/debian
cc1a78bfd46b: Pull complete
Digest: sha256:de3eac83cd481c04c5d6c7344cd7327625a1d8b2540e82a8231b5675cef0ae5f
Status: Downloaded newer image for debian:latest
guo@Server218:~$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntunginx latest ca3292d807d3 41 hours ago 177 MB
redis 3.2 e97b1f10d81a 2 weeks ago 99.7 MB
debian latest 8626492fecd3 3 weeks ago 101 MB
ubuntu latest 452a96d81c30 3 weeks ago 79.6 MB
hello-world latest e38bc07ac18e 5 weeks ago 1.85 kB
centos latest e934aafc2206 6 weeks ago 199 MB
guo@Server218:~$
docker pull 下来之后不会运行,仅仅是把一个镜像下载到本地。而run命令则提供了更多的功能。
docker run
下面我们以编程语言届最典型的例子Hello world
来对docker run
举例。
guo@Server218:~$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntunginx latest ca3292d807d3 41 hours ago 177 MB
redis 3.2 e97b1f10d81a 2 weeks ago 99.7 MB
ubuntu latest 452a96d81c30 3 weeks ago 79.6 MB
guo@Server218:~$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
9bb5a5d4561a: Already exists
Digest: sha256:f5233545e43561214ca4891fd1157e1c3c563316ed8e237750d59bde73361e77
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/engine/userguide/
guo@Server218:~$
可见,run命令会首先做一下pull的处理,然后再加载镜像到docker引擎中运行。
docker本身命令
-
docker version
: 打印当前系统docker本身的版本信息。 -
docker info
: 可以看做是更加详细的docker version
。 -
docker ps
类似于linux上查看进程的命令,docker ps用来查看当前运行了那些容器,以及对应的具体服务信息。这条命令对于查看容易的健康状态是非常重要的,而且特别的常用。
操作镜像
-
docker images
: 列出本地已经下载的镜像,类似于Python的pip list
-
docker search
: 列出可以下载的对应名称的镜像列表,类似于Python的pip search
。
等等
操作容器
相对于操作镜像,docker对容器的操作命令更多,功能也更强大。
-
docker stop/restart/pause/start
: 停止,重启,暂停,开启一个容器 -
docker rmi
通过容器id删除一个已经存在的容器,一般要先停止容器才能操作,或者添加--force
选项来强制删除。 -
docker logs
容器异常退出时,可以通过logs命令来查看具体的错误纤细,这一点对于服务的调试很有帮助。
等等
docker attach
和 docker exec
这两个命令都是为了进入一个正在运行的容器而存在的,但是却有很大的不同。从字面意思一上来看其实也有点差距。attach给人的印象就像tmux的attach一样,退出的时候会把正在运行的docker容器也杀死。而exec就像是SSH一样,只是断开“远程连接”,即使是退出也不会导致当前正在运行的容器进程kill掉。单纯用文字来讲,描述起来可能也不清楚,下面还是来举个小例子。
因为docker本身的性质,如果一个命令运行完进程就会结束的话,docker容器自然也会结束。所以我们不能再容器中简单的使用类似于echo hello
这样的bash命令,而是要使用一个会在后台长期运行的命令,这样才能保证容器一直在运行,所以我pull了一个Debian系统的redis镜像。在容器中会启动一个redis-server服务。
docker run -p 9999:6379 -v $PWD/data:/data -d redis:3.2 redis-server --appendonly yes
通过docker ps
命令查看目前docker引擎内正在运行的容器的进程情况,可以看出确实启动了一个运行了redis服务的容器。
guo@Server218:~/dockerlearn$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cf78dfe74342 redis:3.2 "docker-entrypoint..." 2 hours ago Up 2 hours 0.0.0.0:9999->6379/tcp romantic_lamarr
guo@Server218:~/dockerlearn$
鉴于docker attach已经过时,这里就不在新版本的docker中演示了。来看看docker exec的方式。具体的命令格式为:docker exec -it containerid shell-env
.
guo@Server218:~/dockerlearn$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cf78dfe74342 redis:3.2 "docker-entrypoint..." 2 hours ago Up 2 hours 0.0.0.0:9999->6379/tcp romantic_lamarr
guo@Server218:~/dockerlearn$ docker exec -it cf78 /bin/bash
root@cf78dfe74342:/data# ps aux | grep redis
redis 1 0.0 0.4 33316 9464 ? Ssl 04:20 0:09 redis-server *:6379
root 33 0.0 0.0 11128 1004 ? S+ 06:58 0:00 grep redis
root@cf78dfe74342:/data# exit
exit
guo@Server218:~/dockerlearn$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cf78dfe74342 redis:3.2 "docker-entrypoint..." 2 hours ago Up 2 hours 0.0.0.0:9999->6379/tcp romantic_lamarr
guo@Server218:~/dockerlearn$
可以看出,exec方式退出连接到的docker容器之后,容器并没有被销毁,而是依旧在运行着容器自身的服务。
- 使用
Ctrl+p+q
的方式,可以做到退出容器而不关闭容器,这一点需要明确下,后面可能会成为一个大坑。 -
ctrl+d
退出容器且关闭, docker ps 查看无。
Dockerfile
类似于其他的编程语言,从上往下一步步的执行,实现最终的效果。dockerfile是用来构建自己镜像的脚本。它有固定的格式,大致形式如下:
dockerfile 格式
# dockerfile的注释是#符号,和Python,bash等编程语言的注释类似,在合适的地方写注释可以使得构建docker镜像的逻辑更加清楚。
#第一行必须指令基于的基础镜像
From reponame:tagname
#维护者信息
MAINTAINER docker_maintainer [email protected]
#镜像的操作指令
apt-get update && apt-get upgrade
RUN apt-get update && apt-get install -y nginx
RUN echo "\ndaemon off;">>/etc/ngnix/nignix.conf
EXPOSE port1 port2
#容器启动时执行指令
CMD /usr/sbin/ngnix
ENTRYPOINT /usr/sbin/nginx等
指令释义
从上面我们也能看出来,一个标准的dockerfile大致有4个部分组成,分别是: 镜像源、维护者信息、构建指令(比如安装软件,更新系统,设置环境等,具体不止RUN一条,还可能会有ENV, USER等)、CMD以及ENTRYPOINT。下面简要的解释下这些指令对应的作用。
FROM处于非注释dockerfile的第一行,用来指明镜像源的名称,相当于我们在命令行执行了
docker pull xxxx
。tag名缺省为latest
。MAINTAINER: 指明维护者的信息,属于可选项。但是如果想让自己的镜像被别人下载,就需要对应的平台注册下账号,这样方便别人的搜索。
-
构建指令:
- RUN应该是最常用的构建指令了。其作用就类似于在平时宿主系统中使用的bash命令,只不过RUN会在构建镜像的时候再docker引擎中执行罢了。具体格式有两种,shell方式和exec方式:
- shell 方式,RUN COMMAND param1 param2
- exec方式: RUN ['command', 'param1', 'param2',,,]
- USER: 指定docker容器的使用用户,默认是root,也可以指定其他用户,比如对于nginx可以使用
USER www-data
- EXPOSE: 设置对外暴露的端口,可以是多个。容器运行成功后可以通过docker ps 查看到宿主机端口和容器端口的映射关系。
- ENV: 类似于bash中的set指令,在dockerfile中定义的变量可以在后面的指令中应用。
- RUN应该是最常用的构建指令了。其作用就类似于在平时宿主系统中使用的bash命令,只不过RUN会在构建镜像的时候再docker引擎中执行罢了。具体格式有两种,shell方式和exec方式:
-
CMD和ENTRYPOINT:这两个命令都是用来启动一个容器,但是二者稍有不同。
- CMD: 可以有多条,但是只有最后一条会生效,前面的将被覆盖掉。
- ENTRYPOINT:一个dockerfile只能有一个ENTRYPOINT命令,用来指定容器启动的命令。相当于把镜像改造成了一个可执行程序般来使用。
CMD命令有一点尤其需要注意,docker run命令如果指定了参数会把CMD里的参数覆盖: (这里说明一下,如:docker run -it ubuntu /bin/bash 命令的参数是指/bin/bash 而非 -it ,-it只是docker 的参数,而不是容器的参数
-- -- http://cloud.51cto.com/art/201411/457338.htm
写完一个Dockerfile之后,就可以基于其来构建自己的镜像了。具体的命令如下:
# 为了防止将多余的文件构建到镜像中,Dockerfile一般会放到一个空目录下,然后再次目录下执行如下命令
docker build -t tagname .
这样,一个自定义的镜像就完成了。可以自己尝试下,至于publish自己的镜像,这里就不多做叙述了,毕竟目前平台上已经有太多太多优秀的已经构建好的镜像了,我们直接pull过来使用就好,自己构建的不一定有人家做的好。
实战小例子
我本人对Python情有独钟,因此参考Docker构建Python镜像来实战下。
guo@Server218:~$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntunginx latest ca3292d807d3 42 hours ago 177 MB
redis 3.2 e97b1f10d81a 2 weeks ago 99.7 MB
ubuntu latest 452a96d81c30 3 weeks ago 79.6 MB
hello-world latest e38bc07ac18e 5 weeks ago 1.85 kB
guo@Server218:~$
通过pull方式
guo@Server218:~$ docker search python3.6
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
microsoft/azure-functions-python3.6 Python 3.6 image for Azure Functions 2 [OK]
lucascosta/serverless-python3.6 Serverless Framework with Python3.6 2 [OK]
sky46821/centos7-python3.6 centos7/python3.6 1
arwineap/docker-ubuntu-python3.6 docker-ubuntu-python3.6 1 [OK]
leondomingo/ubuntu16.04-python3.6.1 1
... ...
guo@Server218:~$ docker pull python:3.6
3.6: Pulling from library/python
3d77ce4481b1: Pull complete
534514c83d69: Pull complete
d562b1c3ac3f: Pull complete
4b85e68dc01d: Pull complete
a60ceaabb01c: Pull complete
ba209b7a7239: Pull complete
235ce1ab7310: Pull complete
bd6e9cb6b441: Pull complete
Digest: sha256:18e515f2cd7fd40c019bce12fda36b9a9c58613cf6fb8d6e58f831ef565a7b81
Status: Downloaded newer image for python:3.6
guo@Server218:~$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntunginx latest ca3292d807d3 43 hours ago 177 MB
python 3.6 d69bc9d9b016 2 weeks ago 691 MB
redis 3.2 e97b1f10d81a 2 weeks ago 99.7 MB
ubuntu latest 452a96d81c30 3 weeks ago 79.6 MB
hello-world latest e38bc07ac18e 5 weeks ago 1.85 kB
guo@Server218:~$ docker run -it python:36 bash
root@8433585070a3:/# pwd
/
root@8433585070a3:/# ls
bin dev home lib64 mnt proc run srv tmp var
boot etc lib media opt root sbin sys usr
在宿主机添加一个用于执行的python文件。
guo@Server218:~/dockerlearn/docker-python36$ mkdir src
guo@Server218:~/dockerlearn/docker-python36$ cd src
guo@Server218:~/dockerlearn/docker-python36/src$ ls
guo@Server218:~/dockerlearn/docker-python36/src$ vim helloworld.py
guo@Server218:~/dockerlearn/docker-python36/src$ cat helloworld.py
#!/usr/bin python
print("Hello Python36")
guo@Server218:~/dockerlearn/docker-python36/src$
guo@Server218:~/dockerlearn/docker-python36$ docker run -v $PWD/src:/home -w /home python:3.6 python helloworld.py
Hello Python36
guo@Server218:~/dockerlearn/docker-python36$
这句话的意思就是,将宿主机的$PWD/src
目录映射到容器的/home
目录下,然后进入容器之后通过-w
参数切换到/home
目录,最后执行命令。
通过Dockerfile方式
刚才演示了Python36,下面通过Dockerfile实现python35的镜像构建。
guo@Server218:~/dockerlearn$ mkdir docker-python35
guo@Server218:~/dockerlearn$ ls
docker-python35 docker-python36 ubuntu-nginx.dockerfile
guo@Server218:~/dockerlearn$ cd docker-python35
guo@Server218:~/dockerlearn/docker-python35$ ls
guo@Server218:~/dockerlearn/docker-python35$
Dockerfile的具体内容为从上面链接copy过来的,大致内容如下:
FROM buildpack-deps:jessie
# remove several traces of debian python
RUN apt-get purge -y python.*
# http://bugs.python.org/issue19846
# > At the moment, setting "LANG=C" on a Linux system *fundamentally breaks Python 3*, and that's not OK.
ENV LANG C.UTF-8
# gpg: key F73C700D: public key "Larry Hastings " imported
ENV GPG_KEY 97FC712E4C024BBEA48A61ED3A5CA953F73C700D
ENV PYTHON_VERSION 3.5.1
# if this is called "PIP_VERSION", pip explodes with "ValueError: invalid truth value ''"
ENV PYTHON_PIP_VERSION 8.1.2
RUN set -ex \
&& curl -fSL "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz" -o python.tar.xz \
&& curl -fSL "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz.asc" -o python.tar.xz.asc \
&& export GNUPGHOME="$(mktemp -d)" \
&& gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$GPG_KEY" \
&& gpg --batch --verify python.tar.xz.asc python.tar.xz \
&& rm -r "$GNUPGHOME" python.tar.xz.asc \
&& mkdir -p /usr/src/python \
&& tar -xJC /usr/src/python --strip-components=1 -f python.tar.xz \
&& rm python.tar.xz \
\
&& cd /usr/src/python \
&& ./configure --enable-shared --enable-unicode=ucs4 \
&& make -j$(nproc) \
&& make install \
&& ldconfig \
&& pip3 install --no-cache-dir --upgrade --ignore-installed pip==$PYTHON_PIP_VERSION \
&& find /usr/local -depth \
\( \
\( -type d -a -name test -o -name tests \) \
-o \
\( -type f -a -name '*.pyc' -o -name '*.pyo' \) \
\) -exec rm -rf '{}' + \
&& rm -rf /usr/src/python ~/.cache
# make some useful symlinks that are expected to exist
RUN cd /usr/local/bin \
&& ln -s easy_install-3.5 easy_install \
&& ln -s idle3 idle \
&& ln -s pydoc3 pydoc \
&& ln -s python3 python \
&& ln -s python3-config python-config
CMD ["python3"]
下面开始构建自己的镜像
docker build -t mypython35 .
构建完成后,我们就可以通过docker来运行自己的容器了。同样,这次也这么来映射下,看看执行的结果如何。
Removing intermediate container 952d0bd576a4
Successfully built 211c8752f9d7
guo@Server218:~/dockerlearn/docker-python35$ docker run -v $PWD/../docker-python36/src:/home -w /home mypython35 python helloworld.py
Hello Python36
guo@Server218:~/dockerlearn/docker-python35$
这次我们使用的helloworld.py依旧是上一步的测试文件,发现可以正常得到执行结果,说明我们通过Dockerfile实现了自己的镜像的制作了。
总结
docker很不错,个人觉得大家都应该对此有点了解,然后就可以自己搭建集群式的环境,或者模拟一些服务场景,这都会是很有帮助的。
关于镜像层面,各大平台提供的镜像基本上能满足我们所有的需求了,但是如果想自己试一试的话,dockerfile会是个很不错的切入口。