Docker-入门到精通
Docker为什么出现?#
-
开发和运维两套环境,而环境配置十分麻烦。
如在Windows上开发,要发布到Linux上运行。Docker给以上问题提出了解决方案:
Java --- Jar(环境)---打包项目带上环境(镜像)---Docker仓库(应用商店)---下载镜像---直接运行
-
Docker的思想来自于集装箱,核心思想:隔离。
即将应用打包装箱,每个箱子是互相隔离的,可以将服务器利用到极致。
| 虚拟机 | Docker |
|---|---|
| linux centos原生镜像(一个电脑) | 隔离镜像(最核心的环境 +jdk +mysql等) |
| 需要开启多个虚拟机 | 运行镜像就可以了 |
| 几GB | 几MB |
官方文档#
https://docs.docker.com/
仓库地址#
https://hub.docker.com/
Docker能做什么?#
传统虚拟机与Docker对比#

Docker的优点#
- 不模拟完整的操作系统,系统内核(kernel)非常小,更少的抽象层(GuestOS:如Centos)
- 容器内的应用直接运行在宿主机的内核,容器本身没有自己的内核,也没有虚拟硬件。
- 每个容器相互隔离,内部都有属于自己的文件系统,互不影响。
Docker实现DevOps(开发、运维)#
-
应用更快速的交付和部署
打包镜像发布测试,一键运行;不再需要写大量帮助文档,安装程序
-
更便捷的升级和扩缩容?
部署应用就和搭积木一样
-
更简单的系统运维
开发和测试的环境高度一致
-
更高效的计算资源利用
内核级别的虚拟化,可以在一个物理机上运行很多的容器实例,服务器性能可以被压榨到极致。
Docker的基本组成#

- 镜像(image):镜像是一种轻量级、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件。它包含运行某个软件所需的所有内容,包括代码、运行时、库、环境变量和配置文件。相当于一个模板,通过这个模板来创建容器服务,可以通过一个镜像创建多个容器。
- 容器(container):独立运行一个或一组应用/基本命令有:启动,停止,删除等/可理解为一个简单的linux系统。
- 仓库(repository):存放镜像的地方(公有/私有)
Docker运行原理#
Docker是一个Client-Server结构的系统,以守护进程运行在主机上。通过Socket从客户端进行访问。
Docker的常用命令#

帮助命令#
docker --help # 帮助信息 docker info # 系统信息,包括镜像和容器的数量帮助文档地址:https://docs.docker.com/engine/reference/
镜像命令#
docker search 镜像名 # 搜索镜像 docker pull 镜像名 # 下载镜像Docker采用联合文件系统,不同镜像的相同文件无需再次下载:

容器命令#
docker run [options] 镜像名/id [command] # 建立容器并启动: [options]: --name=容器名 # 命名容器以区分不同容器 -d # 在后台运行容器(必须有一个前台进程,否则进程会自动关闭) -it # 使用交互方式运行,进入容器查看内容 -p 主机端口:容器端口 # 暴露指定容器端口 -P # 暴露容器所有端口 [command]: /bin/bash # 控制台
其他命令#
docker start/restart/stop/kill 容器名/id docker logs -tf --tail 显示的日志条数 容器名/id # 查看日志 docker top 容器名/id # 查看容器中的进程信息 docker inspect 容器名/id # 查看镜像的元数据 docker exec -it 容器名/id /bin/bash # 通常容器以后台方式运行,需要进入其中修改配置:进入容器后开启一个新终端 docker attach 容器名/id # 进入容器正在执行的终端 docker cp 容器名/id:容器内路径 主机文件路径 # 从容器内拷贝文件到主机上 ## 练习部署Nginx 搜索镜像:docker search Nginx 拉取镜像:docker pull Nginx 运行镜像:docker run -d - -name Nginx_1 -p 3344:80 -d 后台运行 - -name 容器名称 -p 3344 是本机端口,80是容器端口,访问本机3344端口会自动映射容器80端口 运行测试: curl localhost:3344 输出:
Welcome to nginx!
Welcome to nginx!
If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.
For online documentation and support please refer to
nginx.org.
Commercial support is available at
nginx.com.
Thank you for using nginx.
## 练习部署 Tomcat
直接拉去运行:docker run -it -rm tomcat
-rm 表示 停止容器运行后,直接删除容器
正常运行:docker run -d -p 3355:8080 --name tomcat_1 tomcat
crul [http://localhost:3355/](http://localhost:3355/) 测试运行;
进入容器内部:
docker exec -it tomcat_1 /bin/bash
容器内部是一个阉割版的linux,把容器内的webapp.dis目录复制到webapp目录下
## 练习部署es+kibana
// - - net somenetwork
拉取运行:docker run -d --name elasticsearch -p 9200:9300 -e "discovery.type=single-node" elasticsearch:7.6.2
测试:curl localhost:9200
查看使用状态 docker starts
容器启动JVM调试:
docker run -d --name elasticsearch -p 9200:9300 -e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms64m -Xmx512m -XX:+UseG1GC" elasticsearch:7.6.2
## **Docker可视化面板**
- 管理镜像工具
- portainer
Portainer图形化管理工具
使用:
docker run -d -p 8088:9000 \
--restart=always -v /var/run/docker.sock:/var/run/docker.sock --privileged=true portainer/portainer
- -restart 重启策略
-v 表示容器数据卷挂载
访问:localhost:8088
- Rancher(CI/CD)
# Docker镜像详解[#](https://www.cnblogs.com/koktlzz/p/14105026.html#docker镜像详解)

## UnionFS(联合文件系统)[#](https://www.cnblogs.com/koktlzz/p/14105026.html#unionfs联合文件系统)
- 联合文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下。联合文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
- 特性:一次同时加载多个文件系统,但从外面看起来只能看到一个文件系统。联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录。
## 镜像加载原理[#](https://www.cnblogs.com/koktlzz/p/14105026.html#镜像加载原理)
Docker的镜像实际由一层一层的文件系统组成:
- bootfs(boot file system)主要包含bootloader和kernel。bootloader主要是引导加载kernel,完成后整个内核就都在内存中了。此时内存的使用权已由bootfs转交给内核,系统卸载bootfs。可以被不同的Linux发行版公用。
- rootfs(root file system),包含典型Linux系统中的/dev,/proc,/bin,/etc等标准目录和文件。rootfs就是各种不同操作系统发行版(Ubuntu,Centos等)。因为底层直接用Host的kernel,rootfs只包含最基本的命令,工具和程序就可以了。
- 分层理解
所有的Docker镜像都起始于一个基础镜像层,当进行修改或增加新的内容时,就会在当前镜像层之上,创建新的容器层。
容器在启动时会在镜像最外层上建立一层可读写的容器层(R/W),而镜像层是只读的(R/O)。

docker commit -m="描述信息" -a="作者" 容器id 目标镜像名:[tag] # 编辑容器后提交容器成为一个新镜像
#例如
docker commit -a="MnysLjj" -m="add webapp files" ebe56f1e5f21 tomcat_2:1.0
容器变镜像
Exit # 从容器中退回主机
CTRL+Q+P # 容器不停止退出
docker ps # 显示当前运行的容器
-a # 带出历史运行过的容器
docker rm 容器名/id # 删除指定容器
docker rm &(docker ps -aq) # 删除全部容器
docker rmi 镜像名/id 删除镜像
# 容器数据卷[#](https://www.cnblogs.com/koktlzz/p/14105026.html#容器数据卷)

## 什么是容器数据卷?[#](https://www.cnblogs.com/koktlzz/p/14105026.html#什么是容器数据卷)
为了实现数据持久化,使容器之间可以共享数据。可以将容器内的目录,挂载到宿主机上或其他容器内,实现同步和共享的操作。即使将容器删除,挂载到本地的数据卷也不会丢失。
## 使用容器数据卷[#](https://www.cnblogs.com/koktlzz/p/14105026.html#使用容器数据卷)
使用命令:
dokcer run -it -v 主机内目录:容器内目录 镜像名/id
将容器内目录挂载到主机内目录上,通过**docker inspect**命令查看该容器即可以看到挂载信息:

就会在容器内的挂载目录下发现相同的文件(test.txt),从而实现了容器和主机的文件同步和共享:

## 匿名挂载[#](https://www.cnblogs.com/koktlzz/p/14105026.html#匿名挂载)
docker run -d -v 容器内目录 镜像名/id # 匿名挂载
匿名挂载后,使用**docker volume ls**命令查看所有挂载的卷:

## 具名挂载[#](https://www.cnblogs.com/koktlzz/p/14105026.html#具名挂载)
docker run -d -v 卷名:容器内目录 镜像名/id # 具名挂载

]([https://cdn.jsdelivr.net/gh/koktlzz/NoteImg@main/20201211164244.png](https://cdn.jsdelivr.net/gh/koktlzz/NoteImg@main/20201211164244.png))可以发现挂载的卷:volume01,并通过**docker volume inspect 卷名** 命令找到主机内目录:[

所有docker容器内的卷,在未指定主机内目录时,都在:_/var/lib/docker/volumes/卷名/*data* 下,可通过具名挂载可以方便的找到卷,因此广泛使用这种方式进行挂载。
## 数据卷容器[#](https://www.cnblogs.com/koktlzz/p/14105026.html#数据卷容器)

## 使用案例

# 1.导入(载入)镜像
docker load -i mysql.tar
# 2.查看镜像名称
docker images
# 3.创建目录
mkdir -p mysql/data
mkdir -p mysql/conf
# 4.运行容器挂载容器文件
docker run \
--name mysql_8.0 \ # --name 容器名称
-e MYSQL_ROOT_PASSWORD=123456 \ # -e 环境变量,这里是设置密码
-p 3306:3306 \
-v /temp/mysql/conf/hmy.cnf:/etc/mysql/conf.d/hmy.cnf \ # -v 数据卷,冒号前是主机目录,冒号后是容器目录,挂载目录实现数据共享。
-v /temp/mysql/data:/var/lib/mysql \
-d \ # 后台运行
mysql:8.0 # 对应镜像

黄色虚线箭头:是容器内文件直接向数据卷中寻找,docker在数据卷中自动化配置对应目录 绿色虚线箭头:以文件目录方式挂载,自己创建对应目录 ,容器查找,耦合度较高需要自己管理
# DockerFile

Dockerfile是用来构建docker镜像的文件



## 构建步骤:

编写一个dockerfile文件
#指定基础镜像
FROM java:8-alpine
COPY ./app.jar /tmp/userservice.jar
#暴露端口
EXPOSE 8090
#入口命令,java项目的启动命令
ENTRYPOINT java -jar /tmp/userservice.jar
随后运行命令:
docker build -t javaweb:1.0 .
#docker build -f 文件路径 -t 标签 . # 文件名为Dockerfile时可省略且最后的.不要忽略
docker run # 运行镜像
docker push # 发布镜像
## dockerfile命令[#](https://www.cnblogs.com/koktlzz/p/14105026.html#dockerfile命令)
| 命令 | 效果 |
|---|---|
| FROM | 基础镜像:Centos/Ubuntu |
| MAINTAINER | 镜像作者+邮箱 |
| RUN | 镜像构建的时候需要运行的命令 |
| ADD | 为镜像添加内容(压缩包) |
| WORKDIR | 镜像工作目录(进入容器时的目录) |
| VOLUME | 挂载的目录 |
| EXPOSE | 暴露端口配置 |
| CMD/ENTRYPOINT | 指定这个容器启动时要运行的命令(CMD替代先前命令,ENTRYPOINT在先前命令后追加) |
| COPY | 类似于ADD,将文件拷贝到镜像中 |
| ENV | 构建时设置环境变量 |
## 构建过程[#](https://www.cnblogs.com/koktlzz/p/14105026.html#构建过程)
- 每个保留关键字(指令)都必须是大写字母
- 从上到下顺序执行
- "#" 表示注释
- 每一个指令都会创建提交一个新的镜像层并提交
## 构建实例[#](https://www.cnblogs.com/koktlzz/p/14105026.html#构建实例)

docker run -it --name container02 --volumes from container01 镜像名/id # 将两个容器进行挂载
每一个VOLUME NAME对应一个挂载的卷,由于挂载时未指定主机目录,因此无法直接找到目录。
建立挂载关系后,只要使用命令在主机内新建一个文件:
touch /home/mountdir/test.txt

DockerCompose

version: "3.8"
services:
mysql:
image:mysql:5.7.25
evironment:
MYSQL_ROOT_PASSWORD: 123
volumes:
- /tmp/mysql/data:/var/lib/mysql
- /tmp/mysql/conf/hmy.cnf:/etc/mysql/conf.d/hmy.cnf
web:
build: .
ports:
-8090:8090
下载安装docker-compose
sudo curl -L "https://github.com/docker/compose/releases/download/1.25.5/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
下载完成后,将该文件设置为可执行:
sudo chmod +x /usr/local/bin/docker-compose
运行下面的命令验证是否安装成功并查看Compose 的版本:
docker-compose --version

使用案例

Dockerfile文件编写:
##指定基础镜像
#FROM java:8-alpine
#
#COPY ./app.jar /tmp/app.jar
#
#
##入口命令,java项目的启动命令
#ENTRYPOINT java -jar /tmp/app.jar
#docker-compose.yml
version: "3.2"
services:
nacos:
image: nacos/nacos-server
environment:
MODE: standalone
ports:
- "8848:8848"
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: 123456
volumes:
- "$PWD/mysql/data:/var/lib/mysql"
- "$PWD/mysql/conf:/etc/mysql/conf.d"
userservice:
build: ./user-service
orderservice:
build: ./order-service
gateway:
build: ./gateway
ports:
- "8080:8080"
在DockerCompose部署微服务集群中,服务之间的访问可以使用 服务名称互相访问
例如:
spring:
datasource:
# 不再是localhost,而是mysql:3306 访问连接数据库
url: jdbc:mysql://mysql:3306/mysql?useSSL=false
cloud:
nacos:
config:
# 不再是 localhsot或者127.0.0.1,使用服务名称nacos
server-addr: nacos:8848
pom.xml
app
org.springframework.boot
spring-boot-maven-plugin

将cloud-demo目录 复制到Linux tmp目录下 运行命令
docker-compose up -d
# up 创建并且启动容器
# -d 后台运行
# 启动完成查看日志
docker-compose logs -f |servicename|
# 重启 一个或多个微服务
docker restart servicename1 servicename2
Docker的镜像仓库
Docker registry docker仓库,也可以叫docker 镜像注册中心,将镜像注册到docker 仓库中


命令如下:
docker run -d \
--restart=always \
--name registyry \
-p 5000:5000 \
-v registry-data:/var/lib/registry \
registry
带图形界面版本

version: '3.0'
services:
registry:
image: registry
volumes:
- ./registry-data:/var/lib/registry
ui:
image: joxit/docker-registry-ui
ports:
- 9090:80
environment: #环境变量
- REGISTRY_TITLE=标题
- REGISTRY_URL=http://registry:5000
depends_on: #依赖镜像,这里是依赖registry镜像
- registry
配置Docker信任地址

# 打开并且修改文件
vi /etc/docker/daemon.json
{
"insecure-registries":["http://127.0.0.1:9090"]
}
重新加载文件,重启docker
systemctl daemon-reload
systemctl docker
创建docker-compose.yml文件,并运行:
docker-compose up -d
访问:localhost:9090

列如:
docker iamges
# 打包以下拉,已有的镜像 mysql:8.0 ,构建到本地私仓库(服务注册),别名mysql:1.0
docker tag mysql:8.0 127.0.0.1:9090/mysql:1.0
docker images
#推送到私有镜像仓库
docker push 127.0.0.1:9090/mysql:1.0
#访问:localhost:9090
#拉取
docker pull 127.0.0.1:9090/mysql:1.0
私有的镜像仓库可能需要登录Docker login,
没有权限可能在打标签的时候 docker tag 镜像名称 docker用户名/别名:版本

Docker网络
理解Doker0#


-
linux可以ping通docker容器内部,因为docker0的ip地址为172.17.0.1,容器为172.17.0.2。
-
原理:我们每启动一个docker容器,docker就会给容器分配一个默认的可用ip,我们只要安装了docker,就会有一个网卡docker0(bridge)。网卡采用桥接模式,并使用veth-pair技术(veth-pair就是一堆虚拟设备接口,成对出现,一段连着协议,一段彼此相连,充当一个桥梁。)。
-
这时我们退出容器,回到主机再次观察主机的ip地址:

我们惊奇地发现了一个新网络13: vethda1df4b@if12,对应容器内网络地址的12: eth@if13。
-
容器和容器之间是可以互相ping通的:容器1→Docker0→容器2

docker中的所有网络接口都是虚拟的 ,转发效率高。删除容器后,对应的网桥也随之删除。
--link#
若编写一个微服务并连接数据库,如果数据库ip改变,如何根据容器名而不是ip访问容器?显然,直接使用容器名是无法ping通容器内部的:


](https://cdn.jsdelivr.net/gh/koktlzz/ImgBed@master/20201212200748.png)然而反向就不可以ping通,这是因为--link的本质是把需要连接的容器名/id写入启动容器的配置文件中,即增加了一个ip和容器名/id的映射:[

自定义网络#
我们使用命令:
docker network ls # 查看所有的docker网络

- bridge:桥接(docker默认)/
- none:不配置网络 /
- host:和宿主机共享网络
docker run 命令默认带有一个参数--net bridge,此处的bridge指的就是docker0。如果我们不想使用docker0,那如何创建一个新的网络呢?
docker network create --driver 网络模式 --subnet 子网ip --gateway 网关 网络名



网络连通#

docker中的网络模式有:
目前已经不建议使用这种方式。
这时我们可以在容器启动命令中加入一个选项:--link,使得我们可以根据容器名来访问容器。
docker run -d -P --link 容器名/id 镜像名/id
对于建立在不同网络下(docker0, newnet)的两个容器tomcat01和tomcat02,他们的网段不同,因此是无法彼此ping通容器内部的:


](https://cdn.jsdelivr.net/gh/koktlzz/ImgBed@master/20201212203803.png)这个功能类似于将一个容器赋予多个ip地址,同样可以用docker network inspect命令查看网络连通后,该网络的变化:[

原本newnet网络中只含有tomcat02,现在增加了tomcat01,因此可以连通。
这时我们需要通过docker network connect命令打通容器与网络之间的连接:
docker network connect 网络名 容器名/id
curl localhost:3344