Jade Dungeon

docker

镜像的实现原理

Docker 镜像是怎么实现增量的修改和维护的? 每个镜像都由很多层次构成,Docker 使用 Union FS 将这些不同的层结合到一个镜像中去。

通常 Union FS 有两个用途, 一方面可以实现不借助 LVM、RAID 将多个 disk 挂到同一个目录下,另一个更常用的就是将一个只读的分支和一个可写的分支联合在一起, Live CD 正是基于此方法可以允许在镜像不变的基础上允许用户在其上进行一些写操作。 Docker 在 AUFS 上构建的容器也是利用了类似的原理。

安装

Ubuntu1804安装最新的dokcer:

sudo apt-get install docker.io

安装之后启动 Docker 服务。

sudo service docker start

测试安装是否成功:

sudo docker run hello-world

摘自docker mannual上的一段话:

Manage Docker as a non-root user The docker daemon binds to a Unix socket instead of a TCP port. By default that Unix socket is owned by the user root and other users can only access it using sudo. The docker daemon always runs as the root user. If you don’t want to use sudo when you use the docker command, create a Unix group called docker and add users to it. When the docker daemon starts, it makes the ownership of the Unix socket read/writable by the docker group

大概的意思就是:docker进程使用Unix Socket而不是TCP端口。 而默认情况下,Unix socket属于root用户,需要root权限才能访问。

解决方法1

使用sudo获取管理员权限,运行docker命令

解决方法2

docker守护进程启动的时候,会默认赋予名字为docker的用户组读写Unix socket的权限, 因此只要创建docker用户组,并将当前用户加入到docker用户组中, 那么当前用户就有权限访问Unix socket了,进而也就可以执行docker相关命令

sudo groupadd docker         #添加docker用户组
sudo gpasswd -a $USER docker #将登陆用户加入到docker用户组中
newgrp docker                #更新用户组
docker ps                    #测试docker命令是否可以使用sudo正常使用

如果安装成功,在本地找不到hello-world镜像会到仓库里下载。

编辑/etc/default/docker文件,在其中的DOCKER_OPTS中配置加速器地址:

DOCKER_OPTS="--registry-mirror=https://registry.docker-cn.com"

重新启动服务:

$ sudo service docker restart

Ubuntu16.04+、Debian8+、CentOS7 对于使用 systemd 的系统, 请在/etc/docker/daemon.json中写入如下内容(如果文件不存在请新建该文件):

{"registry-mirrors":["https://registry.docker-cn.com"]}

之后重新启动服务:

$ sudo systemctl daemon-reload
$ sudo systemctl restart docker

镜像

搜索镜像

docker search <keyword>

下载镜像

我们可以从 Docker Hub 网站来搜索镜像,Docker Hub 网址为:https://hub.docker.com/

我们也可以使用 docker search 命令来搜索镜像。

Docker Hub 仓库下载一个 Ubuntu 12.04 操作系统的镜像。

$ sudo docker pull ubuntu:12.04

该命令实际上相当于:

$ sudo docker pull registry.hub.docker.com/ubuntu:12.04

即从注册服务器registry.hub.docker.com中的 ubuntu 仓库来下载标记为 12.04 的镜像

完成后,即可随时使用该镜像了,例如创建一个容器,让其中运行 bash 应用。

$ sudo docker run -t -i ubuntu:12.04 /bin/bash

下载加速CDN

国内从 DockerHub 拉取镜像有时会遇到困难,此时可以配置镜像加速器。Docker 官方和国内很多云服务商都提供了国内加速器服务,例如:

  • 网易:https://hub-mirror.c.163.com/
  • 阿里云:https://<你的ID>.mirror.aliyuncs.com
  • 七牛云加速器:https://reg-mirror.qiniu.com

当配置某一个加速器地址之后,若发现拉取不到镜像,请切换到另一个加速器地址。 国内各大云服务商均提供了 Docker 镜像加速服务,建议根据运行 Docker 的云平台选择对应的镜像加速服务。

阿里云镜像获取地址:https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors, 登陆后,左侧菜单选中镜像加速器就可以看到你的专属地址了

Ubuntu14.04、Debian7Wheezy

对于使用 upstart 的系统而言,编辑/etc/default/docker文件,在其中的DOCKER_OPTS中配置加速器地址:

DOCKER_OPTS="--registry-mirror=https://registry.docker-cn.com"

重新启动服务:

$ sudo service docker restart

Ubuntu16.04+、Debian8+、CentOS7

对于使用 systemd 的系统,请在/etc/docker/daemon.json中写入如下内容(如果文件不存在请新建该文件):

{"registry-mirrors":["https://reg-mirror.qiniu.com/"]}

之后重新启动服务:

$ sudo systemctl daemon-reload
$ sudo systemctl restart docker

从私有仓库下载镜像

有时候官方仓库注册服务器下载较慢,可以从其他仓库下载。 从其它仓库下载时需要指定完整的仓库注册服务器地址。例如:

$ sudo docker pull dl.dockerpool.com:5000/ubuntu:12.04

新版本Docker需要SSL验证,私有仓库可以自己建SSL证书; 对于没有证书的仓库,本地可以不开SSL连接,方法是在配置文件添加:

# ubntu   /etc/default/docker
# CentOS  /etc/sysconfig/docker

OPTIONS='--selinux-enabled --insecure-registry dl.dockerpool.com:5000'

显示本地镜像

$ sudo docker images
REPOSITORY       TAG      IMAGE ID      CREATED      VIRTUAL SIZE
ubuntu           12.04    74fe38d11401  4 weeks ago  209.6 MB
ubuntu           precise  74fe38d11401  4 weeks ago  209.6 MB
ubuntu           14.04    99ec81b80c55  4 weeks ago  266 MB
ubuntu           latest   99ec81b80c55  4 weeks ago  266 MB
ubuntu           trusty   99ec81b80c55  4 weeks ago  266 MB
...

在列出信息中,可以看到几个字段信息

  • 来自于哪个仓库,比如 ubuntu
  • 镜像的标记,标记来自同一个仓库的不同镜像。比如 14.04
  • 它的 ID 号(唯一)
  • 创建时间
  • 镜像大小

存出镜像

如果要导出镜像到本地文件,可以使用docker save命令。

$sudo docker save -o ubuntu_14.04.tar ubuntu:14.04

载入镜像

可以使用docker load从导出的本地文件中再导入到本地镜像库,例如:

$ sudo docker load --input ubuntu_14.04.tar

$ sudo docker load < ubuntu_14.04.tar

这将导入镜像以及其相关的元数据信息(包括标签等)。

移除本地镜像

如果要移除本地的镜像,可以使用docker rmi命令。注意docker rm命令是移除容器。

$ sudo docker rmi training/sinatra
Untagged: training/sinatra:latest
Deleted: 5bc342fa0b91cabf65246837015197eecfa24b2213ed6a51a8974ae250fedd8d
Deleted: ed0fffdcdae5eb2c3a55549857a8be7fc8bc4241fb19ad714364cbfd7a56b22f
Deleted: 5c58979d73ae448df5af1d8142436d81116187a7633082650549c52c3a2418f0

*注意:在删除镜像之前要先用 docker rm 删掉依赖于这个镜像的所有容器。

提交镜像

容器运行之后状态改变了,使用 docker commit 命令来提交更新后的副本。

$ sudo docker commit -m "Added json gem" -a "Docker Newbee" 0b2616b0e5a8 ouruser/sinatra:v2
4f177bd27a9ff0f6dc2a830403925b5360bfe0b93d476f7fc3231110e7f71b1c

其中:

  • -m来指定提交的说明信息,跟我们使用的版本控制工具一样;
  • -a可以指定更新的用户信息;
  • 0b2616b0e5a8:容器 ID是说明根据哪个容器ID来创建镜像;
  • ouruser/sinatra:v2最后指定目标镜像的仓库名和 tag 信息。

创建成功后会返回这个镜像的ID信息。

例如,我们升级镜像中的ubuntu:

更新镜像之前,我们需要使用镜像来创建一个容器。

runoob@runoob:~$ docker run -t -i ubuntu:15.10 /bin/bash
root@e218edb10161:/# 

在运行的容器内使用apt-get update命令进行更新。

在完成操作之后,输入exit命令来退出这个容器。

此时 ID 为e218edb10161的容器,是按我们的需求更改的容器。我们可以通过命令docker commit来提交容器副本。

runoob@runoob:~$ docker commit -m="has update" -a="runoob" e218edb10161 runoob/ubuntu:v2
sha256:70bf1840fd7c0d2d8ef0a42a817eb29f854c1af8f7c59fc03ac7bdee9545aff8

构建镜像

我们使用命令docker build, 从零开始来创建一个新的镜像。为此,我们需要创建一个 Dockerfile 文件,其中包含一组指令来告诉 Docker 如何构建我们的镜像。

runoob@runoob:~$ cat Dockerfile 
FROM    centos:6.7                            # 第一条FROM,指定使用哪个镜像源
MAINTAINER      Fisher "fisher@sudops.com"

RUN     /bin/echo 'root:123456' |chpasswd                         # 执行命令
RUN     useradd runoob                                            # 执行命令
RUN     /bin/echo 'runoob:123456' |chpasswd                       # 执行命令
RUN     /bin/echo -e "LANG=\"en_US.UTF-8\"" >/etc/default/local   # 执行命令
EXPOSE  22                                                        # 开放端口
EXPOSE  80                                                        # 开放端口
CMD     /usr/sbin/sshd -D                                    # 启动后命令
  • 每一个指令都会在镜像上创建一个新的层,每一个指令的前缀都必须是大写的。
  • 第一条FROM,指定使用哪个镜像源
  • RUN 指令告诉docker 在镜像内执行命令,安装了什么。。。

然后,我们使用 Dockerfile 文件,通过docker build命令来构建一个镜像。

runoob@runoob:~$ docker build -t runoob/centos:6.7 .
Sending build context to Docker daemon 17.92 kB
Step 1 : FROM centos:6.7
 ---&gt; d95b5ca17cc3
Step 2 : MAINTAINER Fisher "fisher@sudops.com"
 ---&gt; Using cache
 ---&gt; 0c92299c6f03
Step 3 : RUN /bin/echo 'root:123456' |chpasswd
 ---&gt; Using cache
 ---&gt; 0397ce2fbd0a
Step 4 : RUN useradd runoob
......

参数说明:

  • -t :指定要创建的目标镜像名
  • . :Dockerfile 文件所在目录,可以指定Dockerfile 的绝对路径

使用docker images查看创建的镜像已经在列表中存在,镜像ID为860c279d2fec

runoob@runoob:~$ docker images 
REPOSITORY          TAG                 IMAGE ID            CREATED              SIZE
runoob/centos       6.7                 860c279d2fec        About a minute ago   190.6 MB
runoob/ubuntu       v2                  70bf1840fd7c        17 hours ago         158.5 MB
ubuntu              14.04               90d5884b1ee0        6 days ago           188 MB
php                 5.6                 f40e9e0f10c8        10 days ago          444.8 MB
nginx               latest              6f8d099c3adc        12 days ago          182.7 MB
mysql               5.6                 f2e8d6c772c0        3 weeks ago          324.6 MB
httpd               latest              02ef73cf1bc0        3 weeks ago          194.4 MB
ubuntu              15.10               4e3b13c8a266        5 weeks ago          136.3 MB
hello-world         latest              690ed74de00f        6 months ago         960 B
centos              6.7                 d95b5ca17cc3        6 months ago         190.6 MB
training/webapp     latest              6fae60ef3446        12 months ago        348.8 MB

我们可以使用新的镜像来创建容器

runoob@runoob:~$ docker run -t -i runoob/centos:6.7  /bin/bash
[root@41c28d18b5fb /]# id runoob
uid=500(runoob) gid=500(runoob) groups=500(runoob)

从上面看到新镜像已经包含我们创建的用户 runoob。

设置镜像标签

我们可以使用docker tag命令,为镜像添加一个新的标签。

runoob@runoob:~$ docker tag 860c279d2fec runoob/centos:dev

docker tag 镜像ID,这里是 860c279d2fec ,用户名称、镜像源名(repository name)和新的标签名(tag)。

使用 docker images 命令可以看到,ID为860c279d2fec的镜像多一个标签。

runoob@runoob:~$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
runoob/centos       6.7                 860c279d2fec        5 hours ago         190.6 MB
runoob/centos       dev                 860c279d2fec        5 hours ago         190.6 MB
runoob/ubuntu       v2                  70bf1840fd7c        22 hours ago        158.5 MB
ubuntu              14.04               90d5884b1ee0        6 days ago          188 MB
php                 5.6                 f40e9e0f10c8        10 days ago         444.8 MB
nginx               latest              6f8d099c3adc        13 days ago         182.7 MB
mysql               5.6                 f2e8d6c772c0        3 weeks ago         324.6 MB
httpd               latest              02ef73cf1bc0        3 weeks ago         194.4 MB
ubuntu              15.10               4e3b13c8a266        5 weeks ago         136.3 MB
hello-world         latest              690ed74de00f        6 months ago        960 B
centos              6.7                 d95b5ca17cc3        6 months ago        190.6 MB
training/webapp     latest              6fae60ef3446        12 months ago       348.8 MB

上传镜像

用户可以通过docker push命令,把自己创建的镜像上传到仓库中来共享。 例如,用户在Docker Hub上完成注册后,可以推送自己的镜像到仓库中。

$ sudo docker push ouruser/sinatra
The push refers to a repository [ouruser/sinatra] (len: 1)
Sending image list
Pushing repository ouruser/sinatra (3 tags)

容器

列出所有的容器

docker ps -a查看所有现在的容器:

$ sudo docker ps -a                                                                                                                                                                           ──(Sun,Jul26)─┘
CONTAINER ID  IMAGE   COMMAND     CREATED        STATUS                       PORTS   NAMES
1397350faf1b  ubuntu  "/bin/bash" 23 minutes ago Up 23 minutes                        stupefied_galileo    
77b113e911a9  ubuntu  "/bin/bash" 3 hours ago    Exited (0) About an hour ago         dreamy_ardinghelli 

新建容器

docker run命令:

$ sudo docker run --name test -p 8001:8080 -t -i ubuntu:14.04 /bin/bash
root@0b2616b0e5a8:/#   注意,这里的机器名也是容器的ID
  • --name指定容器的名字。
  • -p hostPort:contanerPort把本地端口映射到容器端口。
  • -P把本地端口随机抽一个可用的映射到容器端口。
  • -t会分配一个伪终端,并绑定到标准输入输出上。
  • -i容器的标准输入输出保持打开,以交互模式执行。

如果不指定具体的标记,则默认使用latest标记信息。

注意:启动会创建一个新的容器,要记住容器的ID(就是新机器的机器名)。

docker run创建容器还包括以下一系列操作:

  • 检查本地是否有指定的镜像,没有的话从公共仓库下载。
  • 根据镜像创建容器。
  • 分配一个文件系统,并在只读的镜像外包一层可读写层。
  • 从宿主主机配置的网桥接口中接一个虚拟接口到容器中。
  • 从地址池中配置一个ip地址给容器。
  • 执行用户指定的程序。
  • 挂靠完毕后容器终止。

启动已经存在的容器

docker start命令。

$ sudo docker start -a -i 容器名
  • -a:绑定输入输出。
  • -i:绑定交互。

以守护态运行

-d参数指定以守护态运行:

$ sudo docker run  -d ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
1e5535038e285177d5214659a068137486f96ee5c2e85a4ac52dc83f2ebe4147

容器启动后会返回一个唯一的id,也可以通过docker ps命令来查看容器信息。

$ sudo docker ps
CONTAINER ID  IMAGE         COMMAND               CREATED        STATUS       PORTS NAMES
1e5535038e28  ubuntu:14.04  /bin/sh -c 'while tr  2 minutes ago  Up 1 minute        insane_babbage

要获取容器的输出信息,可以通过docker logs命令。

$ sudo docker logs insane_babbage
hello world
hello world
hello world
. . .

连接容器

在使用-d参数时,容器启动后会进入后台。此时想要进入容器,可以通过以下指令进入:

  • docker attach
  • docker exec:推荐大家使用 docker exec 命令,因为此退出容器终端,不会导致容器的停止。
$ docker exec -it <窗口ID> <要执行的命令> [命令的参数 ...]
  • -i: 交互式操作。
  • -t: 终端。

例:

$ docker exec -it ubuntu-prd-01 bash 

$ docker exec -it ubuntu-prd-01 ps -aux

查看应用程序日志

docker logs [ID或者名字]可以查看容器内部的标准输出。

runoob@runoob:~$ docker logs -f bf08b7f2cd89
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
192.168.239.1 - - [09/May/2016 16:30:37] "GET / HTTP/1.1" 200 -
192.168.239.1 - - [09/May/2016 16:30:37] "GET /favicon.ico HTTP/1.1" 404 -
  • -f: 让docker logs像使用tail -f一样来输出容器内部的标准输出。

从上面,我们可以看到应用程序使用的是 5000 端口并且能够查看到应用程序的访问日志。

查看应用程序容器的进程

我们还可以使用docker top来查看容器内部运行的进程

runoob@runoob:~$ docker top wizardly_chandrasekhar
UID     PID         PPID          ...       TIME                CMD
root    23245       23228         ...       00:00:00            python app.py

查看容器的端口绑定

docker port命令可以让我们快捷地查看端口的绑定情况。

runoob@runoob:~$ docker port adoring_stonebraker 5000
127.0.0.1:5001

检查应用程序底层信息

使用 docker inspect 来查看 Docker 的底层信息。它会返回一个 JSON 文件记录着 Docker 容器的配置和状态信息。

runoob@runoob:~$ docker inspect wizardly_chandrasekhar
[
    {
        "Id": "bf08b7f2cd897b5964943134aa6d373e355c286db9b9885b1f60b6e8f82b2b85",
        "Created": "2018-09-17T01:41:26.174228707Z",
        "Path": "python",
        "Args": [
            "app.py"
        ],
        "State": {
            "Status": "running",
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 23245,
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2018-09-17T01:41:26.494185806Z",
            "FinishedAt": "0001-01-01T00:00:00Z"
        },
......

导出和导入容器

如果要导出本地某个容器,可以使用docker export命令。

$ docker export 1e560fca3906 > ubuntu.tar

导出容器1e560fca3906快照到本地文件ubuntu.tar

这样将导出容器快照到本地文件。

可以使用docker import从容器快照文件中再导入为镜像,以下实例将快照文件ubuntu.tar导入到镜像test/ubuntu:v1:

$ cat docker/ubuntu.tar | docker import - test/ubuntu:v1

此外,也可以通过指定 URL 或者某个目录来导入,例如:

$ docker import http://example.com/exampleimage.tgz example/imagerepo

终止容器

可以使用docker stop来终止一个运行中的容器。

此外,当Docker容器中指定的应用终结时,容器也自动终止。 例如对于上一章节中只启动了一个终端的容器,用户通过exit命令或Ctrl+d 来退出终端时,所创建的容器立刻终止。

终止状态的容器可以用docker ps -a命令看到。例如:

sudo docker ps -a
CONTAINER ID        IMAGE                    COMMAND                CREATED             STATUS                          PORTS               NAMES
ba267838cc1b        ubuntu:14.04             "/bin/bash"            30 minutes ago      Exited (0) About a minute ago                       trusting_newton
98e5efa7d997        training/webapp:latest   "python app.py"        About an hour ago   Exited (0) 34 minutes ago                           backstabbing_pike

处于终止状态的容器,可以通过docker start命令来重新启动。

此外,docker restart命令会将一个运行态的容器终止,然后再重新启动它。

重启应用容器

已经停止的容器,我们可以使用命令docker start来启动。

runoob@runoob:~$ docker start wizardly_chandrasekhar
wizardly_chandrasekhar

docker ps -l查询最后一次创建的容器:

#  docker ps -l 
CONTAINER ID        IMAGE                             PORTS                     NAMES
bf08b7f2cd89        training/webapp     ...        0.0.0.0:5000->5000/tcp    wizardly_chandrasekhar

正在运行的容器,我们可以使用docker restart命令来重启

删除容器

$sudo docker rm <contaner>

Supervisor配置服务

Docker容器在启动的时候开启单个进程,比如,一个 ssh 或者 apache 的 daemon 服务。 但我们经常需要在一个机器上开启多个服务,这可以有很多方法:

  • 最简单的就是把多个启动命令放到一个启动脚本里面,启动的时候直接启动这个脚本,
  • 另外就是安装进程管理工具。
apt-get install supervisor
mkdir -p /var/log/supervisor
# vi /root/supervisord.conf
supervisord.conf 
[supervisord]
nodaemon=true

[program:sshd]
command=/usr/sbin/sshd -D
supervisord -c /root/supervisord.conf