介绍容器(Docker)的原理和常见的命令。

概念

Docker 是对于 Linux 容器的一种封装,提供了见到那的容器使用接口。Linux 容器是一种虚拟化技术,在正常的进程外面加上了一个保护层,实现了该进程与底层系统的隔离。Docker 是使用Go语言实现的。

(1)虚拟机(virtual machine)和Docker 的联系和区别

虚拟机是在一个系统中运行了另一个系统。比如在mac 电脑中装上了windows(stupid);在windows 中装上了Ubuntu。

相同点:软件开发中环境可能存在两种问题:”程序能在我的机器上跑“ (It worked on my machine);程序中新老版本不兼容,如python2 和python3 中一些库函数。虚拟机和docker 都是为了解决软件开发中的环境配置问题。

不同点:虚拟机是操作系统级别。虚拟机本身需要占用几百兆运行,并且启动慢。Docker 是对于进程的封装,不是模拟完整的操作系统。所以启动快,占用资源少。

联系:Docker 相比于虚拟机有了更大程度的共享,更小级别的隔离。虚拟机虚拟出的是一套硬件,而Docker 虚拟出的是一个操作系统,不同容器之间是可以共享一个操作系统的。

(2)Docker的核心概念

1). 镜像(Image): 是一个只读的模板,类似安装操作系统的iso 文件,通过镜像可以用来完成各种应用的部署。

Image 是一个特殊的文件系统。包含了运行时所需要的程序、库、资源、配置等文件。镜像不包含任何动态的数据。dockerfile 中最开始的 from 表示的就是镜像

2). 容器(Container):镜像和容器的唯一区别是容器的最上一层是可读可写的。所以容器 =镜像+ 可读层。类似一个操作系统,可以create, start, run,stop,kill,pause和rm操作。

最后打成某个程序,就是容器。

3). 仓库:存放镜像的一个场合,仓库分为公开仓库和私有仓库

如果说Docker 是面向对象的,那么容器与镜像的关系类似于面向对象编程中的对象与类。

Docker 的优点

Docker 是一个用于开发,交付和运行应用程序的开放平台。Docker 使您能够将应用程序与基础架构分开,从而可以快速交付软件。借助 Docker,您可以与管理应用程序相同的方式来管理基础架构。通过利用 Docker 的方法来快速交付,测试和部署代码,您可以大大减少编写代码和在生产环境中运行代码之间的延迟。

快速,一致地交付您的应用程序 响应式部署和扩展 在同一硬件上运行更多工作负载

Docker 的缺点:对于开发者是非常友好的,但是对于运维人员,想要在已经封装好的容器上做一些调整,是很难的。

(3)在Ubuntu 18 上的安装和使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 安装常规操作,读取软件列表
$ sudo apt-get update

# 添加 docker 官方 GPG key
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

# 设置稳定仓库
$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"
   
# 安装 Docker Engine
$ sudo apt-get update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io 

安装比较简单,这种安装的Docker不是最新版本,不过对于学习够用了,依次执行下面命令进行安装。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
sudo apt install docker.io
sudo systemctl start docker

sudo systemctl enable docker

sudo service docker start #启动守护进程

docker -v

sudo service docker stop #关闭守护进程

#【查】查看本地已有的镜像 Docker images
docker images

# 增加
docker run docker_name

测试是否安装成功

1
sudo docker run hello-world

设置用户组(这样用户就不用使用 sudo 查看docker 的信息)

设置非root账号不用sudo直接执行docker命令

参考这里进行设置

1
2
3
$ sudo groupadd docker          #添加docker用户组
$ sudo gpasswd -a $USER docker  #将当前用户添加至docker用户组
$ newgrp docker                 #更新docker用户组

Docker 常用命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#启动docker
sudo service docker start

#停止docker
sudo service docker stop

#重启docker
sudo service docker restart

#列出Docker CLI命令
docker
docker container --help

#显示Docker版本和信息
docker --version
docker version
docker info

Execute Docker image
docker run hello-world

#列出镜像列表
docker image ls

#列出docker容器 (running, all, all in quiet mode)
docker container ls
docker container ls --all
docker container ls -aq


#搜索:

docker search ubuntu

#下载镜像

docker pull ubuntu

#查看所有的镜像

docker images

#删除docker 包

sudo rm -rf /var/lib/docker

我们需要确认容器有在运行,可以通过 docker ps 来查看

1
2
3
CONTAINER ID:容器ID
NAMES:自动分配的容器名称

在容器内使用docker logs命令,查看容器内的标准输出

可以使用 docker logs ID来查看log,也可以通过后面名称来查docker logs silly_poitras

停止容器 docker stop id or docker_name 这样的形式。

查看预装情况

1
2
3
4
5
docker -v
docker-compose -v

docker ps # 查看目前的容器名
docker-compose ps #查看当前项目的容器状态

https://github.com/ashokc/Serving-Flask-on-Docker/blob/master/quoteserver/docker-compose.yml

从这里看,应该是定义了全局的docker 名称,包括 app_host 等一系列名称,可以在之后的debug 中看看,这些名称是否是全局可达的。

使用docker 的时候默认是sudo 权限,为了避免麻烦,这个时候可以新建一个docker 用户组,将使用的用户加入到docker 用户组中.

  • 创建docker 用户组
1
sudo groupadd docker
  • 将某个用户加入到 docker 用户组
1
sudo usermod -aG docker jeng
  • 重启docker 服务
1
sudo systemctl restart docker
  • 重新登录该账户

Dockerfile 书写相关

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
FROM nginx  # 这个是基础镜像,之后都是基于该镜像

# 镜像的作者信息,包含所有者和联系人
MAINTAINER

# shell格式 (所以是可以根据本地的shell命令构建dockerfile)
RUN <命令行命令>
# <命令行命令> 等同于,在终端操作的 shell 命令。 

# exec格式
RUN ["可执行文件", "参数1", "参数2"]
# 例如:
# RUN ["./test.php", "dev", "offline"] 等价于 RUN ./test.php dev offline

RUN <command> (shell模式)
RUN [ "executable", "param1", "param2" ] (exec模式)


# 多行执行run
RUN yum install wget
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz"
RUN tar -xvf redis.tar.gz
# 更加建议这种书写,只创建一层镜像

RUN yum install wget \
    && wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \
    && tar -xvf redis.tar.gz

# 是从上下文目录复制文件或者目录到指定路径
COPY home* /mydir/

ENV
# 设置环境变量,在后序的指令中,可以使用这个变量

VOLUME
# 定义数据卷,用于挂载数据,避免因重启而丢失重要数据

EXPOST <PORT>
# 声明端口,告诉应用程序容器内的程序会使用的端口,在运行时使用 -p 参数映射
# docker处于安全的目的,不会自动打开端口。

USER 
# 指定镜像使用什么用户去运行
USER nginx

WORKDIR <dirpath>
# 在 Dockerfile 文件中,WORKDIR一般是绝对路径,也可是相对路径,不过,是相对于上一个WORKDIR的路径。 

# docker 很多情况下是用来打包一个程序,如果docker 打包了这个python 程序(你可以把所有依赖工具打包进docker镜像, 然后用 ENTRYPOINT 指向python脚本本身,当然也可以使用 cmd 指向脚本。但是通常用 ENTRYPOINT 表明docker 镜像只是用来执行这个python 脚本的,不希望用于其他的操作)
ENTRYPOINT 和CMD 的组合使用: ENTRYPOINT 指定默认的运行命令,CMD指定默认的运行参数。比如

# 例子一
FROM ubuntu:trusty
ENTRYPOINT ["/bin/ping","-c","3"]
CMD ["localhost"] 

# 例子二

ENTRYPOINT [ "python" ] # 这个是命令
# Run app.py when the container launches
CMD [ "app.py","run","--host","0.0.0.0"] # 这个是参数

dockerfile常见的一些写法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# install system dependencies
RUN apt-get update \
    && apt-get -y install gcc make \
    && rm -rf /var/lib/apt/lists/*
# 最后的rm 也是可以减少镜像的大小,是一种优化策略

# install dependencies
RUN pip install --no-cache-dir --upgrade pip

RUN pip install --no-cache-dir -r requirements.txt
# --no-cache-dir              Disable the cache.



# 如果在国内为了方便测试可以使用国内镜像
RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

部署

cd 到某个路径中,该路径中有 dockerfile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# build 的命令  -> image 。最后的一个 . 表示上下文路径, 当执行 build 时候,会将该路径下的所有内容打包,所以一定要把没有必要的文件删掉
docker build .
docker build --tag  image_name .
# run 的命令 -> docker ps 中可查
docker run --name deployML -p localport:dockerport image_name
# 实现的是端口映射


docker exec -it container_name  # docker 进入容器里面

docker stop my_container

docker 替换国内镜像

请首先执行以下命令,查看是否在 docker.service 文件中配置过镜像地址。

1
$ systemctl cat docker | grep '\-\-registry\-mirror'

如果该命令有输出,那么请执行 $ systemctl cat docker 查看 ExecStart= 出现的位置,修改对应的文件内容去掉 --registry-mirror 参数及其值,并按接下来的步骤进行配置。

(1)直接修改 /etc/docker/daemon.json (docker 版本 >= 1.10 时) 内容为:

阿里云的docker 镜像需要注册,这里使用的是腾讯、百度和网易的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
    "runtimes": {
        "nvidia": {
            "path": "nvidia-container-runtime",
            "runtimeArgs": []
        }
    },
    "registry-mirrors": [
    "https://mirror.ccs.tencentyun.com",
    "https://mirror.baidubce.com",
    "https://hub-mirror.c.163.com"
  ]
}

修改之后重启服务

1
2
systemctl daemon-reload
systemctl restart docker

docker-compose

Docker-Compose 是 Docker 的一种编排服务,是一个用于在 Docker 上定义并运行复杂应用的工具,可以让用户在集群中部署分布式应用。

通过 Docker-Compose 用户可以很容易地用一个配置文件定义一个多容器的应用,然后使用一条指令安装这个应用的所有依赖,完成构建。Docker-Compose 解决了容器与容器之间如何管理编排的问题。

Docker Compose 工作原理图

Compose 中有两个重要的概念

  • 服务 (service) :一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。
  • 项目 (project) :由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义。

(1) stop all the runing containers

1
 docker-compose stop

(2) restart the containders ( after using the stop command)

1
 docker-compose restart

(3) Starts the containers that were started with docker-compose up:

1
 docker-compose start

(4) Stop all running containers, and remove them and the network:

1
 docker-compose down [--rmi all]

使用以下的命令理解更强

1
docker-compose -f docker-compose.yml up -d hello-world

-d 表示在后台启动容器

hello-world 是这个名字

docker-comose 中一些术语

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
version: '2.0'
services:
    notebook:
        build:
            context: ./notebook
        ports:
            - '8888:8888'
        volumes:
            - ./notebook:/home/jovyan
        links:
            - milvus
    milvus:
        image: milvusdb/milvus:0.9.1-cpu-d052920-e04ed5
        ports:
            - '19530:19530'
            - '19121:19121'
        volumes:
            - ./milvus/db:/var/lib/milvus/db
            - ./milvus/conf:/var/lib/milvus/conf
            - ./milvus/logs:/var/lib/milvus/logs
            - ./milvus/wal:/var/lib/milvus/wal

services :多个容器的集合

build: 配置构建时,Compose 会利用它自动构建镜像,该值可以是一个路径,也可以是一个对象,用于指定 Dockerfile 参数

1
2
3
4
5
6
7
build: ./dir
---------------
build:
    context: ./dir
    dockerfile: Dockerfile
    args:
        buildno: 1

ports: 对外暴露的端口定义 和 exose 对应, “宿主机端口:容器暴露端口”

1
2
3
ports:   # 暴露端口信息  - "宿主机端口:容器暴露端口"
- "8763:8763"
- "8763:8763"

links: 服务之间可以使用服务名称相互访问,links 允许定义一个别名,从而使用该别名访问其它服务

参考文献

阿里云的docker 的使用

Docker 学习新手笔记:从入门到放弃

制作 docker 的教程:

迈出使用Docker的第一步,学习第一个Docker容器

有背景图家具替换demo:http://47.92.49.9:9998/ 白背景家具替换demo:http://47.92.49.9:9999/