服务端开发(5) Docker
2023-08-09 14:53:19 # NJU # 服务端开发

1. 容器

1.1 什么是容器

容器是另外一种轻量级的虚拟化,容器是共用主机内核,利用内核的虚拟化技术隔离出一个独立的运行环境,拥有独立的一个文件系统,网络空间,进程空间视图等

image-20230314195622248

1.2 容器与虚拟机

从虚拟化层看容器,轻量级、高性能是核心价值

  • 容器是在Linux内核实现的轻量级资源隔离机制

  • 虚拟机是操作系统级别的资源隔离,容器本质上是进程级的资源隔离

容器 虚拟机
image-20230314195912283 image-20230314195927526

2. Docker

只能运行在 Linux 环境,即使在 windows 系统提供了 Docker Desktop,底层也是运行在 Linux 虚拟机中的

2.1 Windows下的两类容器

Windows Container

Linux Container

image-20230314202011156

2.2 Docker 的三部分

image-20230314202234086

2.3 Docker 基本命令

1
2
3
4
5
6
7
docker
docker container --help
docker --version
docker version
docker info
docker image ls
docker pull

搜索镜像

docker search mongo

image-20230314204426093

docker run 命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
docker run hello-world
-d: 后台运行容器,并返回容器ID
-i: 以交互模式运行容器,通常与 -t 同时使用
-t: 为容器重新分配一个伪输入终端,通常与 -i 同时使用
-p: 指定(发布)端口映射,格式为:主机(宿主)端口:容器端口
-P: 随机端口映射,容器内部端口随机映射到主机的高端口
--name="nginx-lb": 为容器指定一个名称
-e username="ritchie": 设置环境变量
--env-file=c:/temp1/t1.txt: 从指定文件读入环境变量
--expose=2000-2002: 开放(暴露)一个端口或一组端口;
--link my-mysql:taozs : 添加链接到另一个容器
-v c:/temp1:/data: 绑定一个卷(volume)
--rm 退出时自动删除容器
-w: 指定容器内的工作目录
1
2
docker run --rm -it -p 8080:80 nginx
# 可以通过宿主机的8080端口访问容器的80端口

busybox镜像

1
docker run --rm -it busybox sh
1
2
3
cat /etc/hosts
cat /proc/version
uname -a

练习:gcc

1
2
docker run --rm -v E:\WorkSpace\Server\gcc-hw:/hw -w /hw –it --name=server nercury/cmake-cpp:gcc-5.2
docker run --rm -v E:\WorkSpace\Server\gcc-hw:/hw -w /hw –it --name=client nercury/cmake-cpp:gcc-5.2

练习: mysql的启动和访问

1
2
3
docker pull mysql:5.7
docker run --name my-mysql -e MYSQL_ROOT_PASSWORD=exampledb20 -d mysql:5.7
docker run -it --rm --link my-mysql:server mysql:5.7 mysql –hserver -uroot -pexampledb20 # 客户端

2.4 数据卷

Docker-managed volume(docker管理卷)

Bind mount volume(绑定挂载卷)

image-20230314211833037

volumes: Docker 管理宿主机文件系统的一部分,默认位于 /var/lib/docker/volumes

命令:

1
2
3
4
5
docker volume ls
docker volume create my_volume
# 使用
docker run -v my_volume:/data -it --rm busybox sh
# 进入命令行后 /data 下的内容就永久存储在 my_volume

2.5 容器网络

none网络,—net=none

host网络,—net=host

bridge网络,—net=bridge , docker0 的 linux bridge

  • 创建的容器默认搭在桥上,不同容器可以通信,并可以路由到宿主机及外部网络

container模式,—net=container:NAME_or_ID

  • 使两个容器使用完全相同的网络,可以共享 localhost

image-20230314211920219

命令:

1
2
3
docker network ls
docker network create my_network
docker network inspect my_network # 查看网络详细信息
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
[
{
"Name": "my_network",
"Id": "44348fb303ee38682ffa76afe17d9cc919ef29dc9de0d83689802952a82f94ac",
"Created": "2023-03-16T11:46:54.154057664Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {},
"Labels": {}
}
]

示例

首先创建两台容器,使用自己创建的网络(172.18.0.1,网关)

1
2
docker run --rm -it --name=first --net=my_network busybox sh # 搭载在刚刚创建的网络
docker run --rm -it --name=second --net=my_network busybox sh # 创建第二个容器
First Second
image-20230316195016589 image-20230316195107330

这两个容器可以 ping 通

image-20230316195254960

再创建一个容器,使用默认的bridge网络

1
docker run --rm -it --name=third busybox sh
Third
image-20230316195400824

显然无法ping通上面两个容器

可以给这个容器再加一个”网卡”,搭载在创建的网络上

1
docker network connect my_network 562ff5169a # 容器id

在不同的网络中有各自的ip

image-20230316195801223

现在就可以ping通了

image-20230316195831410

3. 容器镜像构建与编排

3.1 由 Dockerfile 构建镜像

image-20230316201145944

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
# 基础镜像
FROM python:3.9

# 元数据
LABEL maintainer=tzs919@163.com

# 在基础镜像的基础上执行的linux命令
RUN mkdir -p /home/docker/code/app \
&& mkdir -p /root/.pip

# 拷贝命令,从当前上下文拷贝到目标容器的子目录下
# 上下文:Dockerfile所在的目录上下文
COPY pip.conf /root/.pip/

WORKDIR /home/docker/code/app

# 第二个点为WORKDIR
# 如果有不需要的文件或目录可以添加到.dockerignore
COPY . .

RUN pip install -r requirements.txt

# 指定环境变量
ENV MYNAME="taozhaosheng"\
APP_PORT=80

VOLUME /data

# 对外暴露端口号
EXPOSE $APP_PORT

# 容器启动后要执行的第一个程序
#CMD ["uwsgi","--ini","uwsgi.ini"]
CMD ["python","manage.py","runserver","0.0.0.0:80"]

Dockerfile文件的指令

  • FROM:指定基础镜像,必须为第一个命令
  • RUN:构建镜像时执行的命令
  • ADD:将本地文件添加到容器中,tar类型文件会自动解压
  • COPY:功能类似ADD,但是不会自动解压文件
  • CMD:构建容器后调用,也就是在容器启动时才进行调用
  • ENTRYPOINT:配置容器,使其可执行化。配合CMD可省去“application”,只使用参数,用于docker run时根据不同参数执行不同功能
  • LABEL:用于为镜像添加元数据
  • ENV:设置环境变量
  • EXPOSE:指定与外界交互的端口,容器内的端口号,docker run时加 -P 则会映射一个随机号(宿主机)
  • VOLUME:用于指定持久化目录,docker run时如果没有指定挂载目录,会创建一个volume
  • WORKDIR:工作目录,类似于cd命令
  • USER:指定运行容器时的用户名或UID
  • ARG:用于指定传递给构建运行时的变量
  • ONBUILD:用于设置镜像触发器

构建镜像

1
2
3
4
5
# 生成镜像
# 最后一个点为 context
docker build -t mysite:latest .
# 运行容器
docker run -d --name=mysite -p 8081:80 mysite:latest

访问http://localhost:8081/admin/

8081-admin

1
2
docker exec -it mysite bash # 通过shell与容器交互
docker container inspect mysite # 查看容器详细信息

image-20230316204413073

Docker build

3.2 镜像分层

写时复制(COW,Copy-On-Write)

docker history \ 查看镜像的层

image-20230315231523381

将所有的 RUN 指令合并为一个

  • Dockerfile 中的每个指令都会创建一个新的镜像层
  • 镜像层将被缓存和复用
  • 当 Dockerfile 的指令修改了,复制的文件变化了,或者构建镜像时指定的变量不同了,对应的镜像层缓存就会失效
  • 某一层的镜像缓存失效之后,它之后的镜像层缓存都会失效
  • 镜像层是不可变的,如果我们在某一层中添加一个文件,然后在下一层中删除它,则镜像中依然会包含该文件(只是这个文件在 Docker 容器中不可见了)

优化:把变化最少的部分放在Dockerfile的前面,这样可以充分利用镜像缓存

  • 例如将 RUN pip install -r requirements.txt 放在 COPY . . 之前

3.3 服务编排工具,docker-compose

Compose 项目是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排

一个单独的 docker-compose.yml 模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目 (project)

Compose 的默认管理对象是项目,通过子命令对项目中的一组容器进行便捷地生命周期管理

Compose 中有两个重要的概念

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

使用微服务架构的系统一般包含若干个微服务,每个微服务一般部署多个实例。如果每个服务都要手动启停,那么效率低,维护量大

docker-compose.yml

Docker-compose指令详解

Docker三剑客之docker-compose

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
version: '3.6'
services:
fluentd:
build:
context: ./fluentd
environment:
- ES_HOST=elasticsearch
- ES_PORT=9200
ports:
- "24224:24224"
depends_on:
- elasticsearch

flask:
build:
context: ./container
ports:
- "8090:80"
depends_on:
- fluentd
logging:
driver: fluentd
options:
fluentd-address: localhost:24224
tag: docker.flask

elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:6.3.2
environment:
- cluster.name=docker-cluster
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ulimits:
memlock:
soft: -1
hard: -1
ports:
- "9200:9200"

kibana:
image: docker.elastic.co/kibana/kibana:6.3.2
ports:
- "5601:5601"
depends_on:
- elasticsearch

image-20230316210453332

YAML 文件

image-20230316210159136

docker-compose常用命令

  • docker-compose —help
  • docker-compose up -d
    • 该命令十分强大,它将尝试自动完成包括构建镜像,(重新)创建服务,启动服务,并关联服务相关容器的一系列操作
  • docker-compose ps、docker-compose ps —services
  • docker-compose images
  • docker-compose stop
    • 终止整个服务集合
  • docker-compose stop nginx
    • 终止指定的服务(这有个点就是启动的时候会先启动 depends_on 中的容器,关闭的时候不会影响到depends_on中的)
  • docker-compose logs -f [services…]
    • 查看容器的输出日志
  • docker-compose build [SERVICE…]
  • docker-compose rm nginx
    • 移除指定的容器
  • docker-compose up -d —scale flask=3 organizationservice=2
    • 设置指定服务运行的容器个数

ports、expose、links、depends_on

docker-compose的ports、expose、links、depends_on使用技巧

docker-compose.yml格式参考

image-20230327160829434