docker初识与实践

Posted by Rimin on 2020-02-16

认识docker

docker(github 地址), 是一个开放源代码软件项目,让应用程序部署在“软件货柜下”的工作可以自动化进行,借此在Linux操作系统上,提供一个额外的软件抽象层,以及操作系统层虚拟化的自动管理机制。 Docker利用Linux核心中的资源分离机制,例如cgroups,以及Linux核心名字空间,来创建独立的容器。

虚拟机 VS docker

虚拟化技术大家比较熟悉的是虚拟机

所谓虚拟机,通常来说就是通过一个虚拟机监视器 ( Virtual Machine Monitor ) 的设施来隔离操作系统与硬件或者应用程序和操作系统,以此达到虚拟化的目的。

但是虚拟机有一个最大的弊端,就是性能低下。可以看以下这张图:

由于没有了虚拟操作系统和虚拟机监视器这两个层次,大幅减少了应用程序运行带来的额外消耗

docker的产生,为开发,测试,运维等带来了巨大额便利,不仅如此,由于当下是云计算时代,应用的开发也逐渐趋向服务化甚至微服务化,docker应用更加广泛。
口说无凭,可以看一下docker 官方对 Docker 在工作上带来的提升做了调查研究,分别从工作效率的提升和技术设计投入的减少等方面数据化了 Docker 所做出的突出贡献:

image2

docker 的核心技术

docker 的核心技术实现归结于此三大技术:

  • NameSpace: 命名空间是 Linux 核心在 2.4 版本后逐渐引入的一项用于运行隔离的模块. 以进程为例,通过 PID Namespace,在这个空间中运行的进程,完全感知不到外界系统中的其他进程或是其他进程命名空间中运行的进程。于是,利用 PID Namespace,Docker 就实现了容器中隔离程序运行中进程隔离这一目标。
  • Contrl Group: 资源控制组 ( 常缩写为 CGroups ) 是 Linux 内核在 2.6 版本后逐渐引入的一项对计算机资源控制的模块。虚拟化除了制造出虚拟的环境隔离同一物理平台运行的不同程序之外,另一大作用就是控制硬件资源的分配,CGroups 的使用正是为了这样的目的。
  • Union File System: 是一种能够同时挂载不同实际文件或文件夹到同一目录,形成一种联合文件结构的文件系统。Docker 创新的将其引入到容器实现中,用它解决虚拟环境对文件系统占用过量,实现虚拟环境快速启停等问题。后面接触到docker volume(docker 数据卷)时,就会发现他们都存储在统一的目录如: /var/lib/docker/volumes/8d47f68f0aad71a7d091cb0904c827/_data(可以通过docker inspect containerid 查看目录地址),同时,文件的更新像git一样只是将修改的部分记录在案,再更新源文件,大大减少了存储空间的使用。
docker的核心组成

docker的四大核心概念的介绍

  1. 镜像:
    所谓镜像,可以理解为一个只读的文件包,其中包含了虚拟环境运行最原始文件系统的内容。
  2. 容器:
    如果把镜像理解为编程中的类,那么容器就可以理解为类的实例。在容器技术中,容器就是用来隔离虚拟环境的基础设施,而在 Docker 里,它也被引申为隔离出来的虚拟环境。
  3. 数据卷:
    在以往的虚拟机中,我们通常直接采用虚拟机的文件系统作为应用数据等文件的存储位置。为了保证数据的独立性,我们通常会单独挂载一个文件系统来存放数据。而在 UnionFS 的加持下,除了能够从宿主操作系统中挂载目录外,还能够建立独立的目录持久存放数据,或者在容器间共享。在 Docker 中,通过这几种方式进行数据共享或持久化的文件或目录,我们都称为数据卷 ( Volume )。
  4. 网络:
    在 Docker 中,实现了强大的网络功能,我们不但能够十分轻松的对每个容器的网络进行配置,还能在容器间建立虚拟网络,将数个容器包裹其中,同时与其他网络环境隔离。

image6

目前 Docker 官方为我们提供了五种 Docker 网络驱动,分别是:Bridge DriverHost DriverOverlay DriverMacLan DriverNone Driver

  1. Bridge Driver: 默认网络驱动程序. 如果未指定驱动程序,则这是您正在创建的网络类型. 当您的应用程序在需要通信的独立容器中运行时,通常会使用网桥网络。
  2. Host Driver : 对于独立容器,取消容器与Docker主机之间的网络隔离,并直接使用主机的网络. host仅可用于Docker 17.06及更高版本上的集群服务.
  3. Overlay Driver: 覆盖网络将多个Docker守护程序连接在一起,并使群集服务能够相互通信. 还可以使用覆盖网络来促进群集服务和独立容器之间或不同Docker守护程序上的两个独立容器之间的通信. 这种策略消除了在这些容器之间进行操作系统级路由的需要。
  4. MacLan Driver: 为容器分配MAC地址,使其在网络上显示为物理设备。
  5. None Driver: 对于此容器,禁用所有联网. 通常与自定义网络驱动程序一起使用. none不适用于群体服务。

docker 实践

首先先安装docker, 安装的环境可以是 linux, mac ox, window。 其中mac ox, window分别安装的是docker for mac, doker for window (具体 mac 和window 的版本还有要求可在官网查询)。其原理实际上只是在mac或者 window的环境上造了一个linux的环境,如下图所示:
image3

这里以 Mysql + NodeJs 的服务为例,构建镜像并用docker compose 构建成容器集群。

搭建并启动Mysql数据库容器服务
  1. 寻找镜像源(镜像库地址)及确定版本。

  2. 执行以下操作(5.7为版本号):

1
$ docker pull mysql:5.7
  1. 查看本地镜像,就会发现:
1
$ docker images

镜像列表里已经有了 mysql:5.7 的镜像。
image4

  1. 使用:可以在宿主机器中直接登录(注意,前提是宿主机已经安装mysql及可执行mysql命令),宿主机器可以直接连接该数据库。即以下操作:
1
$ docker run -d -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 mysql:5.7

接着在宿主机登录容器中的 mysql

1
$  mysql -h 127.0.0.1  -P 3306 -u root -p

(然后输入密码,注意 -h和 -P需要写全,否则可能会报错)

构建Node服务的相关镜像文件

开始自己构建镜像,这里推荐直接使用VS Code 的插件docker,安装好之后接着 command + shift + p 使用docker add 后面就会自动创建docker 配置文件。

如下,为Dockfile配置文件:
关于Dockerfile的配置项,可以参考官方文档

1
2
3
4
5
6
7
8
FROM node:10  
ENV NODE_ENV docker
WORKDIR /usr/app/myprojectname
COPY ["package.json", "package-lock.json*", "npm-shrinkwrap.json*", "./"]
RUN npm install
COPY . .
EXPOSE 3001
CMD npm run dev

对该dockfile简单的说明如下:

FROM node:10
该服务所依赖的环境,如果你的docker已经有node:10的镜像,会直接拉取本地已有的镜像,若没有则会从dockerhub镜像库拉取,这里推荐可以使用极小版的 node:10-alpine

ENV NODE_ENV docker 设定环境变量

WORKDIR /usr/app/myprojectname
设定你的项目在容器中的位置,一个容器可以看成是一个小的linux系统,所以我们可以在容器启动后通过命令:

1
$ docker exec -it ecef8319d2c8 /bin/sh

(ecef8319d2c8 是你的 container id)
直接进入到项目目录下,也可以查看整个容器的文件目录情况。会发现就是一个小的,与外界隔离的linux系统。

COPY ["package.json", "package-lock.json*", "npm-shrinkwrap.json*", "./"]
将本地目录下的指定的文件复制到上面设置的 WORKDIR的项目目录中

RUN npm install 运行你需要运行的命令

COPY . . :.代表当前目录,即将当前目录的文件复制到docker的项目根目录下。

EXPOSE 3001 将端口号暴露出来,后续可直接在宿主机器通过 127.0.0.1:3001暴露出来

CMD npm run dev 设置容器启动后运行的命令

配置好dockerfile文件后, 在项目根目录使用命令:

1
docker build -t yourDockerImageName .

(注意: 你的docker镜像的命名格式最好是 你想上传该镜像的镜像库的注册ID/镜像名, 否则可能会因为命名的不唯一而被拒绝上传)

build完之后可以运行你的镜像了。

1
$ docker run -p 127.0.0.1:3000:3000  yourDockerImageName
构建docker-compose

Compose 是用于定义和运行多容器 Docker 应用程序的工具。在 Docker Compose 里,我们通过一个配置文件,将所有与应用系统相关的软件及它们对应的容器进行配置,之后使用 Docker Compose 提供的命令进行启动,就能一次性启动并管理多个相关联的容器。

这里将上述的构建的mysql容器和node服务器的容器构建docker-compose.yml 配置文件:

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
version: '2.1'

services:
web:
image:
environment:
NODE_ENV: dev
volumes:
- ./log:/usr/app/web/log
links:
- db
depends_on:
- db
ports:
- 3001:3001

db:
image: mysql:5.7
volumes:
- db-data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: 123456
ports:
- 3306:3306


volumes:
db-data: {}

关于docker-compose.yml的配置说明可以详细阅读官网

其中比较难理解的:

  • Volumes配置相关: 该选项是 ${YOUR LOCAL PATH}:{DOCKER'S ABSOLUTE PATH}, 也即可以将宿主机的本地文件和容器的某个绝对路径下的文件共享。什么时候使用?可以在项目有需要存放持久数据文件的时候将本地或者ftp服务器上专门的文件存放位置映射到容器的文件存放目录中。

  • 容器间通信相关:

使用 Dcoker 部署项目常常会生成很多个容器,这些容器默认只能通过 ip 地址进行访问,但新建一个容器所产生的 ip 地址是不可控的,docker 默认额模式是桥接模式,网络地址一般为:172.17.0.0/16172.18.0.0/16, 比如一个主机中有两个容器,一个可能是172.18.0.2/16,另一个是172.18.0.3/16。(可以使用docker network inspect b9a9b34d772 (你的container id) 查看ip)
这就给容器之间通信带来了一定的麻烦。Docker 中使用 Network 来管理容器之间的通信,只要两个 Conteiner 处于同一个 Network 之中,就可以通过容器名去互相通信。

在上述 docker-compose up运行之后,会:

  1. 创建一个名为myqpp_default的网络。
  2. 使用web服务的配置创建容器,它以web这个名称加入myqpp_default的网络。
  3. 使用db服务的配置容器,它以db这个名称加入myqpp_default的网络。

因此总结一下容器间互相通信的方式:

  • 方式一:可以通过使用容器的IP地址来通信。这种方式会导致IP地址的硬编码,不方便迁移,并且容器重启后IP地址可能会改变,除非使用固定的IP地址。
  • 方式2: 可以通过宿主机的IP加上容器暴露出的端口号来通信。这种方式比较单一,只能依靠监听在暴露出的端口的进程来进行有限的通信。
  • 方式3: 可以使用容器名,通过docker的link机制通信。这种方式通过docker的link机制可以通过一个name来和另一个容器通信,link机制方便了容器去发现其它的容器并且可以安全的传递一些连接信息给其它的容器。使用name给容器起一个别名,方便记忆和使用。即使容器重启了,地址发生了变化,不会影响两个容器之间的连接。例如,web这个服务可使用 webapp://db:3306 访问db容器。若有其他容器连接上述服务名为web的容器,可以使用 http://web:3001 访问。
    当然,也可以使用别名:
1
2
3
web
links:
- "db:database" // 使用别名
  • networks 配置相关:networks关键字用于指定自定义网络
    例如:加入已经存在的网络
1
2
3
4
networks:
default:
external:
name: my-pre-existing-network

以下例子定义了front和back网络,实现了网络隔离。其中proxy和db之间只能通过app来实现通信。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
services:
proxy:
build: ./proxy
networks:
- front
app:
build: ./app
networks:
- front
- back
db:
image: postgres
networks:
- back

构建好之后只要在当前路径下使用命令就能启动docker-compose:

1
docker-compose up
总结:docker network相关
  • bridge模式: 默认使用bridge模式, 为容器提供一个独立的网络环境,因此,若要和其他容器连接,可以使用上述的link和服务名的方法。

    1
    $ docker network create my-net
    1
    2
    3
    4
    $ docker create --name my-nginx \
    --network my-net \
    --publish 8080:80 \
    nginx:latest

    断开连接

    1
    $ docker network disconnect my-net my-nginx
  • host模式:该模式会破坏docker容器的网络隔离,使得所有的集群中的容器都使用宿主机的host。但是该模式在docker for mac 和docker for window 下不生效,因此如果你想在这两种环境中连接其他该环境中的容器,可以使用 docker 特有的域名 host.docker.internal 作为宿主网络的域名。(因宿主机ip可能会经常变化)

  • overlay 模式:该模式适用于多个不同的docker进程间的容器通信。即有多个Docker host,希望能够通过Docker swarm连接起来。具体可以参考overlay network

  • macvlan 模式: 略

注意的点

Docker 容器启动时,默认会把容器内部第一个进程,也就是pid=1的程序,作为docker容器是否正在运行的依据,如果 docker 容器pid=1的进程挂了,那么docker容器便会直接退出。

Docker未执行自定义的CMD之前,nginx的pid是1,执行到CMD之后,nginx就在后台运行,bash或sh脚本的pid变成了1。
所以一旦执行完自定义CMD,nginx容器也就退出了。

因此,如果使用pm2来部署node服务, 或者使用ngnix来部署前端服务时,需要注意进程被自动退出问题:
需要加上防止后台运行的设置如:

1
pm2 start ./master.js --no-daemon  # pm2
1
daemon off;   # 关闭后台运行 ngnix.conf

常用docker命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ docker images

$ docker ps // 查看正在运行的镜像

$ docker build -t yourimagename .

$ docker run -it -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0.18 /bin/bash

$ docker commit -a 'username' -m 'test' 7aa8647dcfb5 test-app:test

$ docker tag test-app mydockerapp/test-app:test // 修改tag

$ docker exec -it ecef8319d2c8 /bin/sh

$ docker rmi 89bba6dbdd98 // 删除某个容器

参考链接: