视频教程
https://study.163.com/course/courseMain.htm?courseId=1209453812&trace_c_p_k2=bd8de59cc4fc450ca9c506aea4c9ffb9
第一章 需要了解的概念
1. Docker
是什么? 做什么用的?
2. 镜像的概念
3. 容器的概念
什么是沙箱机制
- 沙箱是一个虚拟系统程序,沙箱提供的环境相对于每一个运行的程序都是独立的,而且不会对现有的系统产生影响
- 沙箱具有非常良好的独立性, 隔离性, 所以能够搭建一些具有高风险的软件进行测试.
- 在沙箱里面运行病毒可以说也是安全操作.
Docker 是什么
Docker
是一个开源的应用容器引擎, 基于 GO语言 并遵从 Apache2.0
协议开源.
Docker
可以让开发者打包他们的应用以及依赖包到一个轻量级,可移植的 容器 中, 然后发布到任何流行的 Linux 机器上, 也可以实现虚拟化.
容器 是完全使用 沙箱机制, 相互之间不会有任何接口(类似 iPhone 的 app), 更重要的是容器性能开销极低.
容器
虚拟机
Docker 容器优势
- 启动快
- 占用资源少
镜像
通过镜像创建容器
光盘, 装系统
镜像里面的内容
仓库
镜像的集中存放地
第二章 基本操作-镜像
1. 镜像的获取
- 从仓库获取镜像
搜索镜像: docker search image_name
- 搜索结果过滤:
是否是官方: docker search --filter "is-official=true" image_name
是否是自动化构建: docker search --filter "is-automated=true" image_name
大于多少颗星: docker search --filter stars=3 image_name
2. 镜像下载
docker pull images_name
3. 镜像的查看
本地镜像的查看: docker images
4. 镜像的删除
本地镜像的删除: docker rmi image_name
5. 镜像上传到docker hub
▶ docker image list
REPOSITORY TAG IMAGE ID CREATED SIZE
hello latest 4e2ff61a0ac6 2 hours ago 206MB
▶ docker tag hello zqphper/hello
▶ docker image list
REPOSITORY TAG IMAGE ID CREATED SIZE
hello latest 4e2ff61a0ac6 2 hours ago 206MB
zqphper/hello latest 4e2ff61a0ac6 2 hours ago 206MB
▶ docker push zqphper/hello
PS: docker tag hello zqphper/hello
一定要把前缀改成自己 docker hub 名称。
第三章 基础操作-容器
1. 创建容器
docker run -itd --name=container_name image_name
可以分开如下写
docker run -i -t -d --name=container_name image_name
-i 表示以交互模式运行容器;
-d 表示后台运行容器, 并返回容器 ID;
-t 为容器重新分配一个伪输入终端
–name 为容器指定名称,自定义名称
image_name 镜像的名字
2. 查看容器
- 查看容器(运行中的)
docker ps
- 查看容器(包括已停止的)
docker ps -a
3. 停止容器
docker stop container_name/container_id
container_name/container_id 意思是,填入 container_name 或者 container_id 任选其一即可. 以下类似.
4. 启动容器
docker start container_name/container_id
5. 重启容器
docker restart container_name/container_id
6. 删除容器
删除容器之前必须先停止容器
docker rm container_name/container_id
第四章 容器的修改及保存
1. 进入容器
docker exec -it container_name/container_id /bin/bash
2. 退出容器
exit
3. 修改容器
每次修改容器中的内容,一旦删除了,就没有了,所以修改完了,需要提交修改,此时,会生成新的
镜像
,之后可以通过这个新的镜像
来生成新的容器。
docker commit -a "author" -m "message" container_name/container_id new_image_name:tag_name
- 参数说明
-a
参数可选,用于指定作者,可以写你的名字-m
参数可选,提交信息,可以说一下你做了哪些修改container_id
该参数为被修改的容器IDnew_image_name
此为新镜像的名字,可自定义tag_name
此为新镜像的标签, 可不写,不写时候标签默认为 latest
4. 保存修改
第五章 容器操作进阶
1. 端口映射
docker run -itd -p 宿主机端口号:容器端口号 --name=自定义的容器名字 images_name/images_id
上述命令就是 新建了一个容器, 在新建的同时对端口做了映射.
2. 文件挂载
docker run -itd -v /宿主机/文件目录/文件名:/容器/目录/文件名
3. 将容器的文件复制到本地
docker cp 容器名:/容器目录/文件名 /宿主机目录/文件名
4. 将本地的文件复制到容器
docker cp /宿主机目录/文件名 容器名:/容器目录/文件名
5. 容器互联
docker run -itd --link 要关联的容器名字:容器在被关联的容器中的别名
docker-php-ext-install xxx
php安装 xxx 插件
6. 修改mysql 密码
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456'
第六章 Dockerfile
1. 什么是Dockerfile
Dockerfile
就是名为Dockerfile
的文件,文件中包含一些Linux命令,Docker
通过读取文件中的命令来组建镜像。
2. Dockerfile
文件内容结构
Dockerfile
一般分为四部分: *基础镜像信息、维护者信息、镜像操作指定和容器启动时执行命令, **#**为Dockerfile
中的注释
3. 运行 Dockerfile
docker build -t image_name:tag_name .(当前文件夹下)
也可以通过 -f
参数来指定 Dockerfile
文件位置
docker build -f /path/Dockerfile -t image_name:tag_name .(当前文件夹下)
4. 命令详解
FORM
:指定基础镜像(一般为操作系统),必须为第一个命令
格式:
FROM <image>
FROM <image>:<tag>
FROM <image>@<digext>
实例:
FROM centos:7.0
MAINTAINER
: 维护者信息
格式:
MAINTAINER <name>
实例:
MAINTAINER zhangsan
RUN
: 构建镜像时执行的命令
格式:
RUN <command>*exec执行*
格式:
RUN ["executable", "param1", "param2"]
实例:
RUN ["/bin/executable", "param1", "param2"]
RUN yun install nginx
ADD
: 将本地文件添加到容器中,tar类型文件会自动解压(网络压缩资源不会被解压),可以访问网络资源,类似wget
格式:
ADD <src>... <dest>
ADD ["<src>",... "<dest>"] 用于支持包含空格的路径
格式:
ADD tes* /mydir/ # 添加所有以“tes”开头的文件
ADD tes?.txt /mydir/ # ? 替代一个单字符,例如:“test.txt”
ADD test relativeDir/ # 添加“test"到 WORKDIR/relatieDir/
ADD test /absoluterDir/ # 添加 “test" 到 /absoluteDir/
注:第一个参数指宿主机文件路径,第二个参数指容器路径
COPY
: 功能类似ADD,但是不会自动解压文件,也不能访问网络资源
CMD
: 构建容器后调用,也就是在容器启动时才进行调用
格式:
CMD ["executable","param1","param2"] (执行可执行文件,优先)
CMD ["param1","param2"] (设置了ENTRYPOINT, 则直接调用ENTRYPOINT添加参数)
CMD command param1 param2 (执行shell内部命令)
实例:
CMD echo "This is a test." | wc -
CMD ["/usr/bin/wc", "--help"]
注:
CMD不同于RUN,CMD用于指定在容器启动时所要执行的命令,而RUN用于指定镜像构建时所要执行的命令。
ENTRYPOINT
: 配置容器,使其可执行化。配合CMD可省去”application“, 只使用其参数
格式:
ENTRYPOINT ["exectable", "param1", "param2"] (可执行文件,优先)
ENTRYPOINT command param1 param2 (shell内部文件)
实例:
FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-C"]
注:
ENTRYPOINT 于 CMD 非常类似,不用的是通过docker run执行的命令不会覆盖 ENTRYPOINT, 而docker run命令中指定的任何参数,都会被当做参数再次传递给ENTRYPOINT.Dockerfile中只允许有一个ENTRYPOINT命令,多指定时会覆盖前面的设置,而只执行最后的ENTRYPOINT指令。
docker run -itd --name=nginx nginx echo 'hello world'
LABEL
: 用于为镜像添加元数据
格式:
LABEL <key>=<value> <key>=<value> <key>=<value> ...
实例:
LABEL version="1.0" description="这是一个nginx镜像”
注:
使用LABEL指定元数据时,一条LABEL指令可以指定一或多条元数据,指定多条元数据时不同元数据之间通过空格分割。推荐将所有的原数据通过一条LABEL指令指定,以免生成过多的中间镜像。
ENV
: 设置环境变量
格式:
ENV <key><value> #<key>之后的所有内容均会被视为其<value>的组成部分,因此,一次只能设置一个变量
ENV <key>=<value> ... # 可以设置多个变量,每个变量为一个 “<key>=<value>”的键值对,如果<key>中包含空格,可以使用\来进行转义,也可以通过 “”来进行标示;另外,反斜线也可以用于续行
实例:
ENV myName John Doe
ENV myDog Rex The Dog
ENV myCat=fluffy
5. 技巧
-
一些经常变动的文件,放在 dockerfile 文件的后面,因为遇到变动,往下的都不会使用缓存
-
添加
.dockerigonre
文件,来忽略不需要的文件 -
数据的持久化方案一
data volume
是-v
后,docker 帮我们存储的数据。容器删除,这个数据还在。只需要-v
指定。例如docker container run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d -v mysql-data:/var/lib/mysql mysql:5.7
▶ docker volume ls DRIVER VOLUME NAME local mysql-data
-
查看
data volume
命令:查看docker volume ls
删除:docker volume rm xx
, 查看路径docker volume inspect xx
▶ docker volume inspect mysql-data [ { "CreatedAt": "2022-04-20T09:15:04Z", "Driver": "local", "Labels": null, "Mountpoint": "/var/lib/docker/volumes/mysql-data/_data", "Name": "mysql-data", "Options": null, "Scope": "local" } ]
-
数据的持久化方案二,文件挂载
docker container run -d -v $(pwd):/app my-cron
. -v 后面改成 本地路径:容器内路径。这里的$(pwd)
代表当前文件夹路径。windows 为${pwd}
第七章 docker 网络
1. 容器为什么能够获取到IP地址?
因为都连接到了一个叫 docker0 的Linux bridge上
▶ docker network ls
NETWORK ID NAME DRIVER SCOPE
8b5056168c45 02-mysql56_default bridge local
c174f557191f 02-mysql57_default bridge local
b93f6238137b _default bridge local
c95b7cff2474 bridge bridge local
63bfa5c00515 downloads_default bridge local
8a501831988d host host local
81307470d1e6 none null local
▶ docker network inspect bridge
[
{
"Name": "bridge",
"Id": "c95b7cff2474eb97b6c331364b86d5f8fd1e5585bff575ce03ce874945cd6dbc",
"Created": "2022-04-19T08:43:42.982079922Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"42917487a6ffcb6b5bd2debcb5ada7cb78d04302443edb57b07bb95b08608803": {
"Name": "box2",
"EndpointID": "a64b7c2d2d5129f48809a76d88f66823bf91c723e16644e38d419f37e2ad4664",
"MacAddress": "02:42:ac:11:00:03",
"IPv4Address": "172.17.0.3/16",
"IPv6Address": ""
},
"d23e2e27d87afa74b7aedd11e71cc1ee274a6602ce17e8db3a1c60cb82cd7a22": {
"Name": "box1",
"EndpointID": "6b497555b8d703ce5f7c567724f4d1f0c5dc57bb86f867320d372825cfcfc871",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enable_icc": "true",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "docker0",
"com.docker.network.driver.mtu": "1500"
},
"Labels": {}
}
]
2. 为什么宿主机可以ping通容器的IP?
容器 test1
, test2
都连接到 docker0
,所以互通。再通过 eth0
连接到外网。
3. 为什么容器之间的IP是互通的?
因为不同容器之间,都是默认连在 docker0
下。所以是互通的。
4. 为什么容器能ping通外网?
因为 容器连到 docker0
下,docker0
通过 eth0
(宿主机网络)连接到 外网。
5. 容器的端口转发是怎么回事?
6. 创建和使用自定义 bridge 网络
-
创建
docker network create -d bridge mybridge
-
查看
▶ docker network ls NETWORK ID NAME DRIVER SCOPE 8b5056168c45 02-mysql56_default bridge local c174f557191f 02-mysql57_default bridge local b93f6238137b _default bridge local c95b7cff2474 bridge bridge local 63bfa5c00515 downloads_default bridge local 8a501831988d host host local bb7ad4c0e970 mybridge bridge local 81307470d1e6 none null local ▶ docker network inspect mybridge [ { "Name": "mybridge", "Id": "bb7ad4c0e970ca29441a76e26eb0503fccf1d544aa8ebe6d45b23114fbf7e1cf", "Created": "2022-04-21T02:35:51.811271Z", "Scope": "local", "Driver": "bridge", "EnableIPv6": false, "IPAM": { "Driver": "default", "Options": {}, "Config": [ { "Subnet": "172.22.0.0/16", "Gateway": "172.22.0.1" } ] }, "Internal": false, "Attachable": false, "Ingress": false, "ConfigFrom": { "Network": "" }, "ConfigOnly": false, "Containers": {}, "Options": {}, "Labels": {} } ]
-
使用
docker container run -d --rm --name box3 --network mybridge busybox /bin/sh -c "while true;do sleep 3600;done"
, 通过--network mybridge
来指定。因为默认是链接到docker0
, 如果指定了,那么和docker0
下的容器是不互通的。 -
box3
容器连接的是mybridge
,现在让box3
也连接默认的bridge
▶ docker network connect bridge box3
▶ docker container inspect box3
[
{
"Id": "f7674477529e7da8b5c9eab0123d9809634914132f44f6d35ee94cf608513831",
"Created": "2022-04-21T02:44:08.7924526Z",
"Path": "/bin/sh",
"Args": [
"-c",
"while true;do sleep 3600;done"
],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 21195,
"ExitCode": 0,
"Error": "",
"StartedAt": "2022-04-21T02:44:09.1457453Z",
"FinishedAt": "0001-01-01T00:00:00Z"
},
"Image": "sha256:1a80408de790c0b1075d0a7e23ff7da78b311f85f36ea10098e4a6184c200964",
.
.
.
"NetworkSettings": {
.
.
.
"Networks": {
"bridge": {
"IPAMConfig": {},
"Links": null,
"Aliases": [],
"NetworkID": "c95b7cff2474eb97b6c331364b86d5f8fd1e5585bff575ce03ce874945cd6dbc",
"EndpointID": "ec49b7a469b7a7e4d103645465f5a314ea859c3f9a9af1b03a2a990961f3e4af",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.4",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:04",
"DriverOpts": {}
},
"mybridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": [
"f7674477529e"
],
"NetworkID": "bb7ad4c0e970ca29441a76e26eb0503fccf1d544aa8ebe6d45b23114fbf7e1cf",
"EndpointID": "91ce461b5850b79a178acab2bd1840b1030333f1bf233e1bac10af1b9eb82aae",
"Gateway": "172.22.0.1",
"IPAddress": "172.22.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:16:00:02",
"DriverOpts": null
}
}
}
}
]
- 移除网络
docker network disconnect bridge box3
7. host 网络
▶ docker network ls
NETWORK ID NAME DRIVER SCOPE
c95b7cff2474 bridge bridge local
8a501831988d host host local
81307470d1e6 none null local
默认会有3个网络
- bridge 新建容器,如果不指定,会使用这个网络。端口转发需要通过
-p 宿主机端口:容器内端口
来指定。 - host 网络,如果指定
--network host
,这个网络的区别是,默认使用宿主机网络,意味着所有的端口,不需要指定,可以直接监听。 - none 网络,这个是一个 断网的网络,没有任何网络连接。
第八章 docker-compose
1. 什么是 docker-compose
快速启动多个容器的方法
version: "3.8"
services:
flask-demo:
container_name: my-flask-demo
image: flask-demo:latest
environment:
- REDIS_HOST=redis-server
networks:
- demo-network
ports:
- 8080:5000
redis-server:
image: redis:latest
networks:
- demo-network
networks:
demo-network:
- 启动
docker-compose up -d
- 关闭
docker-compose stop
- 移除
docker-compose down
- 命令帮助
docker-compose
如果 镜像不存在,则自动去拉取。
2. 如果docker-compose 中的镜像不存在,可以自己指定 Dockerfile 构建。
version: "3.8"
services:
flask-demo:
build: ./flask
image: flask-demo:latest
container_name: my-flask-demo
image: flask-demo:latest
environment:
- REDIS_HOST=redis-server
networks:
- demo-network
ports:
- 8080:5000
redis-server:
image: redis:latest
networks:
- demo-network
networks:
demo-network:
命令: docker-compose build
就会自动去构建。
- 如上构建自定义镜像时,如果名称不为
Dockerfile
,可以自定义,如下build
写法
version: "3.8"
services:
flask-demo:
build:
context: ./flask
dockerfile: Dockerfile.dev
image: flask-demo:latest
container_name: my-flask-demo
image: flask-demo:latest
environment:
- REDIS_HOST=redis-server
networks:
- demo-network
ports:
- 8080:5000
redis-server:
image: redis:latest
networks:
- demo-network
networks:
demo-network:
3. 服务更新(镜像构建发生变化)
-
第一种方法:手动的更新
docker-compose build
-
第二种方法:
docker-compose up -d --build
会去查看镜像是否有变化,如果有变化,则自动去build
-
如果
docker-compose.yml
内容发生变化,则需要重启docker-compose restart
常用的命令
- 删除不用的 services
docker-compose up -d --remove-orphans
- 重启
docker-compose restart
4. docker-compose 网络
docker-compose 中可以指定网络,如下
version: "3.8"
services:
box1:
image: xiaopeng163/net-box:latest
command: /bin/sh -c "while true;do sleep 3600; done"
networks:
- mynetwork1
box2:
image: xiaopeng163/net-box:latest
command: /bin/sh -c "while true;do sleep 3600; done"
networks:
- mynetwork1
- mynetwork2
networks:
mynetwork1:
mynetwork2:
5. 水平扩展
version: "3.8"
services:
flask:
build:
context: ./flask
dockerfile: Dockerfile
image: flask-demo:latest
environment:
- REDIS_HOST=redis-server
redis-server:
image: redis:latest
client:
image: xiaopeng163/net-box:latest
command: sh -c "while true; do sleep 3600; done;"
水平扩展:在 docker-compose.yml
中快速增加 services
的数量。
命令: docker-compose up -d --scale flask=3
flask 是service 的名字,3是数量。
执行完上面,再执行 docker-compose up -d --scale flask=1
会删除上面为我们新增的2个。
负载均衡:
如上, 水平扩展之后,会生成几个容器,然后在其中一个容器中,ping flask
会有不同的结果。因为 docker 默认做好了负载均衡。
6. docker-compose 环境变量
可以通过 .env 文件来指定环境变量
version: "3.8"
services:
flask:
build:
context: ./flask
dockerfile: Dockerfile
image: flask-demo:latest
environment:
- REDIS_HOST=redis-server
- REDIS_PASS=${REDIS_PASSWORD}
networks:
- backend
- frontend
${REDIS_PASSWORD}
在 .env
文件中
REDIS_PASSWORD=ABC123
可以通过命令 dokcer-compose config
来查看配置文件
▶ docker-compose config
networks:
backend: {}
frontend: {}
services:
flask:
build:
context: /Users/zqmac/Desktop/02-daywork/2022/04/0422/compose-env/flask
dockerfile: Dockerfile
environment:
REDIS_HOST: redis-server
REDIS_PASS: ABC123
image: flask-demo:latest
networks:
backend: null
frontend: null
nginx:
depends_on:
flask:
condition: service_started
image: nginx:stable-alpine
networks:
frontend: null
ports:
- published: 8000
target: 80
volumes:
- /Users/zqmac/Desktop/02-daywork/2022/04/0422/compose-env/nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
- /Users/zqmac/Desktop/02-daywork/2022/04/0422/compose-env/var/log/nginx:/var/log/nginx:rw
redis-server:
command: redis-server --requirepass ABC123
image: redis:latest
networks:
backend: null
version: '3.8'
7. 服务依赖
一个 docker-compose.yml
里面有很多服务,所谓依赖,就是服务之间有先后顺序。用depends_on
来指定
version: "3.8"
services:
flask:
build:
context: ./flask
dockerfile: Dockerfile
image: flask-demo:latest
environment:
- REDIS_HOST=redis-server
networks:
- backend
- frontend
redis-server:
image: redis:latest
networks:
- backend
nginx:
image: nginx:stable-alpine
ports:
- 8000:80
depends_on: # 启动顺序,这里指的是 flask 启动完成之后,再启动 nginx
- flask
volumes:
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./var/log/nginx:/var/log/nginx
networks:
- frontend
networks:
backend:
frontend:
8. 健康检查
如上,一个 docker-compose.yml
中有很多服务,可以通过 服务依赖 ,来指定顺序执行。但有可能 服务起来了,但这个服务没有对外提供服务。例如 数据库服务,启动了,但是不提供服务。此时,就需要用到 健康检查。
version: "3.8"
services:
flask:
build:
context: ./flask
dockerfile: Dockerfile
image: flask-demo:latest
environment:
- REDIS_HOST=redis-server
- REDIS_PASS=${REDIS_PASSWORD}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000"]
interval: 30s
timeout: 3s
retries: 3
start_period: 40s
depends_on:
redis-server:
condition: service_healthy
networks:
- backend
- frontend
redis-server:
image: redis:latest
command: redis-server --requirepass ${REDIS_PASSWORD}
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 1s
timeout: 3s
retries: 30
networks:
- backend
nginx:
image: nginx:stable-alpine
ports:
- 8000:80
depends_on:
flask:
condition: service_healthy
volumes:
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./var/log/nginx:/var/log/nginx
networks:
- frontend
networks:
backend:
frontend: