Docker简单使用

曹至梧 2020-03-22 13:38 更新
  1. 安装
  2. 一些概念
  3. 基本命令
  4. Dockerfile
  5. 网络
  6. 分区挂载和数据卷
  7. 镜像服务器
  8. 参考

1. 安装

照着官方文档 https://docs.docker.com/install/linux/docker-ce/ubuntu/ 做吧.

删除旧的包,如果有的话:

sudo apt-get remove docker docker-engine docker.io containerd runc

更新源,并安装依赖的工具:

sudo apt-get update
sudo apt-get install \
     apt-transport-https \
     ca-certificates \
     curl \
     gnupg-agent \
     software-properties-common

添加 Docker 的源:

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo apt-get update
sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"

安装:

sudo apt-get install docker-ce docker-ce-cli containerd.io

安装完成之后, 有一个 docker 命令可供使用. 同时, docker 的服务默认监听在一个 sock 文件上(这样除了命令行工具, 各语言的 API 都很容易实现了).

权限方面, docker 的功能限制于 root 用户, docker 用户组. 所以, 你要么带着 sudo 用, 要么把当前用户加入到 docker 组:

$ sudo groupadd docker
$ sudo gpasswd -a zys docker

重启一下系统吧.

最后, 先安装一个可用的"镜像":

docker pull ubuntu:18.04

这可能需要一点时间, 也可能因为 GFW 的影响而不容易安装成功.

然后作一个 Hello World :

docker run ubuntu:18.04 echo "Hello World"

安装成功.

2. 一些概念

docker 是一套 Linux 的 LXC 管理工具.

LXC (Linux Containers) 的概念类似于虚拟机, 但是形式上有区别. 虚拟机更像是提供"机器环境", 而 LXC 则是提供"运行环境". 单看概念它们的区别有些微妙, 但是 LXC 的特点就是, 同样是提供一套完全隔离的操作系统环境, 它很快, 超级快.

在使用上, 概念上的区别就是, 用虚拟机, 可能更多是想, 做一个电脑, 让它跑起来, 然后再说让这台电脑干什么事.

docker 的使用, 则是做一个环境(一组进程, 还有虚拟的设备等), 用环境来运行一个命令. 环境-命令 是一体的(一个容器), 命令一旦执行完成, 那么一个容器实例的任务就结束了. 所以如果我们想让一个容器像一台电脑那样, 通常我们给它一个像 /usr/bin/sshd -D 这样的命令, 这个容器像一台电脑, 是因为这个命令一直"没有执行完成"的效果.

虚拟机的使用, 一个镜像就是一个安装好的系统. 而 docker 中, 镜像更像是一张安装光盘(刻盘后不能更改), 容器才是安装好的系统. 这种两层式结构相较于传统的"快照"机制, 要好用得多. (如何从真正的系统安装 ISO 镜像得到 docker 镜像, 我现在也不清楚怎么做)

更具体一些, 使用 docker , 就是在指定的环境中执行一条命令, 比如:

docker run ubuntu:14.04 /bin/bash

意思就是, 在 ubuntu:14.04 这个环境中, 执行 /bin/bash 这条命令. 当然, 这条命令显然是瞬间就执行完了的. 如果和这个命令立即有交互, 那么需要一些参数:

docker run -t -i ubuntu:14.04 /bin/bash

-t 是分配一个虚拟终端, -i 是获取当前的输入. 这样你可以立即使用一个终端来和这个环境交互了.

虽然终端是正常使用电脑最常用的交互方式, 不过真实使用 docker 的场景中, 会这样用的机会不会很多的. 通常是直接执行我们要做的事, 比如运行一个服务, 或者执行一系列的计算.

使用 nc 来作一个例子:

docker run -t ubuntu:14.04 nc -l 8000

加上 -t 是因为需要一个终端来显示输出, 否则 nc 的标准输出就没地方去了.

docker 安装之后, 会自动在系统中作一个网桥配置, run 出来的每个容器实例, 都会分配一个桥接的 IP 地址. 上面的命令执行之后, 已经有容器在运行了, 并且在其中, nc 正监听着 8000 端口. 我们要接上去试试, 首先需要找到这个容器实例的 IP 地址.

先看当前运行着的实例列表:

docker ps

你先看到下面的信息:

CONTAINER ID  IMAGE         COMMAND     CREATED        STATUS        PORTS  NAMES
e1d0c9193025  ubuntu:14.04  nc -l 8000  5 seconds ago  Up 4 seconds         silly_curie

记下 ID , 不需要全部, 前面几位就可以了, 然后查看指定容器实例的细节:

docker inspect e1d0

会输出一串 json 字符串, 里面会有这样一节:

"NetworkSettings": {
    "Bridge": "docker0",
    "Gateway": "172.17.42.1",
    "IPAddress": "172.17.0.82",
    "IPPrefixLen": 16,
    "PortMapping": null,
    "Ports": {}
},

可以看到, 容器的 IP 是 172.17.0.82 , 上面的网关 IP 172.17.42.1 就是你"实体机"的.

在实体机使用 telnet 连上去:

telnet 172.17.0.82 8000

现在能看到效果了.

^] 然后 quit 退出, nc 那边结束执行, 命令执行完, 之前的那个容器实例也就关闭了. 你使用 docker ps 看不到. 但是使用:

docker ps -a

就能看到所有的容器实例. 在这里列出的实例是真实存在的, 只是当前并没有运行起来而已. 你可以随时让其中的实例再运行起来:

docker start e1d0c9193025

这样你又可以 telnet , 只是没地方看输出.

回顾上面的过程, 提出两个概念, 镜像容器 .

镜像 指上面的 ubuntu:14.04 这种, 嗯, 这种环境, 这种系统. 后面会讲如何做一个自己的 镜像 .

容器 是在具体的 镜像run 具体的命令, 得到的一个"绑定状态". run 命令执行时的一些参数(比如和实体机的端口映射), 也是"状态"的一部分, run 过之后就不能更改了.

它们的关系, 有些像编程语言中的 实例 . run 时的命令就像是类实例化时的参数. 后面会提到, 你可以删除 容器 , 也可以删除 镜像 . 当你想删除 镜像 , 但是使用它的 容器 还存在时, 你会得到操作失败的提示.

有继承关系, 得利于 AUFS 这些的层级文件系统, 镜像 的构建也是这种层层封装的结果.

3. 基本命令

docker images
显示镜像列表
docker ps
显示容器列表
docker run IMAGE_ID
指定镜像, 运行一个容器
docker start/stop/pause/unpause/kill/restart CONTAINER_ID
操作容器状态
docker tag IMAGE_ID [REGISTRYHOST/][USERNAME/]NAME[:TAG]
给指定镜像命名
docker pull/push NAME:TAG
下载, 推送镜像到 Docker registry server , NAME 部分包括了服务地址
docker rm/rmi CONTAINER_ID/IMAGE_ID
删除容器, 镜像
docker inspect CONTAINER_ID/IMAGE_ID
查看细节信息
docker top CONTAINER_ID
查看指定的运行容器的进程情况
docker info
查看系统配置信息
docker save/load
保存, 恢复镜像信息
docker commit CONTAINER_ID
从容器创建镜像
docker export > xxx.tar
保存一个容器
docker import - < xxx.tar
恢复一个容器
docker cp CONTAINER_ID:PATH HOSTPATH
从镜像复制文件到实体机
docker diff CONTAINER_ID
查看容器相对于镜像的文件变化
docker logs CONTAINER_ID
查看容器日志
docker build
Dockerfile 构建镜像
docker history IMAGE_ID
查看镜像的构建历史

4. Dockerfile

Dockerfile 是记录了镜像是如何被构建出来的配置文件, 可以被 docker 直接执行以创建一个镜像. 它的样子:

FROM ubuntu:14.04
MAINTAINER YS.Zou <>

ADD run /root/run
ADD sources.list /etc/apt/sources.list
ADD id_rsa.pub /tmp/pubkey
ADD requirements /root/requirements

RUN mkdir -p /root/.ssh && \
    cat /tmp/pubkey >> /root/.ssh/authorized_keys && \
    rm -rf /tmp/pubkey
...

CMD ["bash", "/root/run"]

把文件命名为 Dockerfile , 进入文件所在目录, 输入:

docker build .

就可以开始构建过程, 并且得到一个新的镜像了.

Dockerfile 支持一些很简单的命令:

FROM
以哪个镜像为基础开始构建.

MAINTAINER
作者信息.

RUN
运行一条命令.

CMD
docker run IMAGE_ID cmd 这里的默认命令.

ENTRYPOINT
docker run IMAGE_ID cmd 这里的默认命令的前面部分, runcmd 可以作为后续参数.

EXPOSE
声明会用到的端口.

ENV
设置环境变量

ADD
从当前目录复制文件到容器. 会自动处理目录, 压缩包等情况.

COPY
从当前目录复制文件到容器. 只是单纯地复制文件.

VOLUME
声明一个数据卷, 可用于挂载.

USER
RUN 命令执行时的用户.

WORKDIR
RUN, CMD, ENTRYPOINT 这些命令执行时的当前目录.

ONBUILD
前缀命令, 放在上面这些命令前面, 表示生成的镜像再次作为"基础镜像"被用于构建时, 要执行的命令.

build 的过程, 会依次执行上面的命令, 实际上, docker 做的事, 也就是从基础镜像启一个容器, 然后执行一条命令, 修改之后提交此容器为新镜像. 以此类推, 直到所有命令都执行完. 所以在得到最终构建的镜像时, 会生成很多"中间镜像". 而如果 Dockerfile 中某条命令有错, 也是在当前中止, 过程中的"中间镜像"及"当前构建用的容器"仍然存在的.

5. 网络

docker 安装后, 会自动在系统做一个网桥配置 docker0 . 其容器都会分配到此网桥配置下的独立, 私有 IP 地址.

如果你要自己配置桥接, 也可以把 docker0 删除掉. docker run 的时候使用参数 -b 指定你自己配置的网桥.

docker 容器的网络, 是相对于实体机的私有网络. 在网桥配置下, 只要知道 IP 地址, 各容器, 及实体机本身都可以自由通信.

但是在实体机的网卡网络下, docker 容器就不可见了. 要让容器被外界访问到, 最简单的一个方法就是直接做端口映射, 把容器的端口和实体机端口成对连通. docker run 的时候使用 -p 参数就可以指定一对端口映射:

docker run -d -p 5000:22 -p 18888:8888 zys:common

上面的命令, 在启动容器时, 指定的端口映射表示实体机 5000 端口映射到容器 22 端口, 同时 18888 端口映射到容器 8888 端口. 这样做之后, 就可以通过实体机的 5000 端口 ssh 登录到容器了:

ssh root@localhost -p5000
ssh root@realip -p5000

docker run 时还有其它参数可用到控制容器的网络配置:

-p
端口映射

-h
设置主机名, 这个主机名是仅容器自己可见的.

--link=CONTAINER_NAME:ALIAS
把另一个容器的地址配置成一个 ALIAS 主机名.

--dns
配置 DNS 服务器.

6. 分区挂载和数据卷

容器中的文件系统是独立的, 一旦容器被删除, 则文件系统也会被删除. 如果想容器和实体机在文件系统层面打通, 可以把指定目录挂载到容器当中:

docker run -d -p 5000:22 -v /home/zys/temp:/root/volumn zys:common

使用 -v 参数, 就可以把多个实体机目录挂载到容器的文件系统中.

上面是直观的目录挂载. docker 还有自己的一个 数据卷 的概念. 它可以在容器中定义一些目录, 这些目录不使用层级的 AUFS 文件系统, 并且这些目录独立于容器而存在:

docker run -d -p 5000:22 -v /root/a --name=test zys:common

这样, 其 /root/a 目录就是一个数据卷, 如果使用 docker inspect 查看容器, 可以看到类似下面的信息:

"Volumes": {
    "/root/a": "/var/lib/docker/vfs/dir/xxx"
},
"VolumesRW": {
    "/root/a": true
}

其它的容器可以重用这个数据卷:

docker run -d -p 5000:22 --volumes-from=test zys:common

这里的形式有些别扭啊, 数据卷本来是独立于容器, 但是要想重用它, 又必须基于容器的名字.

当所有容器被删除后, 数据卷本身是还存在的, 但是这时好像没办法再去直接使用它了, 不过里面的数据你可以想办法弄到容器里去再作下一步处理.

7. 镜像服务器

docker 的镜像服务器 docker-registry 是 docker 项目的组成部分. 前面在谈 docker 的命令时, 它的 pull/push 命令就是和镜像服务器打交道. 并且, docker 的设计之中, 服务器地址不是单独配置的, 而是作为镜像名称的一部分.

镜像的完整名称是:

127.0.0.1:5000/zephyr/common:latest

各部分的意思:

docker-registry 的实现也是开源的, 在 github https://github.com/dotcloud/docker-registry 上拿下源码就可以跑起来.

拿下源码之后, 项目中有一个 Dockerfile 文件, 我们可以开始构建镜像了. build 之前, 因为 GFW 的原因, 我们可以先把 Dockerfile 调整一下, 包括两部分:

然后开始构建:

docker build -rm -t registry .

完成之后, 你可以得到一个名为 registry 的镜像, 直接运行即可:

docker run -p 5000:5000 registry

访问 http://localhost:5000 能得到响应, 一个 docker-registry 服务就起来了.

现在你可以把镜像提交到上面去:

docker tag xxx 127.0.0.1:5000/zephyr/common
docker push 127.0.0.1:5000/zephyr/common

完成之后, 在浏览器中访问 http://localhost:5000/v1/search 可以看到列表.

获取镜像:

docker pull 127.0.0.1:5000/zephyr/common

docker-registry 本身是设计成一套 Web API 的, 具体文档在 http://docs.docker.com/reference/api/registry_api/ .

docker 本身的服务, 也是有一套基于网络的 API 可供使用的, 文档在 http://docs.docker.com/reference/api/docker_remote_api/ .

8. 参考

评论
粤ICP备18106170号-1