Docker镜像讲解
Docker镜像加载原理
UnionFS (联合文件系统)
UnionFs(联合文件系统):Union文件系统(UnionFs)是一种分层、轻量级并且高性能的文件系统,他支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下( unite several directories into a single virtual filesystem)。Union文件系统是 Docker镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像
特性:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录。
Docker镜像加载原理
docker的镜像实际上由一层一层的文件系统组成,这种层级的文件系统UnionFS。
boots(boot file system)主要包含 bootloader和 Kernel, bootloader主要是引导加载 kernel, Linux刚启动时会加载bootfs文件系统,在 Docker镜像的最底层是 boots。这一层与我们典型的Linux/Unix系统是一样的,包括bootloader和 Kernel。当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已由 bootfs转交给内核,此时系统也会卸载bootfs。
rootfs(root file system),在 bootfs之上。包含的就是典型 Linux系统中的/dev,/proc,/bin,/etc等标准目录和文件。 rootfs就是各种不同的操作系统发行版,比如 Ubuntu, Centos等等。
commit镜像
docker commit 提交容器成为一个新的副本
# 命令和git原理类似
docker commit -m="描述信息" -a="作者" 容器id 目标镜像名:[版本TAG]
容器数据卷
什么是容器数据卷?
总结一句话:为了实现数据持久化,使容器之间可以共享数据。可以将容器内的目录,挂载到宿主机上或其他容器内,实现同步和共享的操作。即使将容器删除,挂载到本地的数据卷也不会丢失。
使用数据卷
docker run -it -v 主机目录:容器目录
# 测试
[root@localhost home]# docker run -it -v /home/ceshi:/home centos /bin/bash
# 启动起来的时候,我们可以通过docker inspect 容器id 来查看挂载情况:(见下图)
实战:安装MySQL
思考:MySQL的数据持久化的问题!
# 获取镜像
[root@localhost home]# docker pull mysql:5.7
# 运行容器,需要做数据挂载! # 安装mysql,需要配置密码,这是要注意的点!
# 官方测试:docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag
# 启动我们的MySQL容器
-d 后台运行
-p 端口映射
-v 卷挂载
-e 环境配置
--name 容器名字
[root@localhost home]# docker run -d -p 3310:3306 -v /home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql:5.7
# 启动成功之后,我们在本地使用sqlyog 连接测试一下
# sqlyog —— 连接到服务器的3310 —— 3310和容器内的3306映射,这个时候我们就可以连接上了!
# 本地测试创建一个数据库,查看一下我们的映射的路径是否ok!
即使将容器删除,挂载到本地的数据卷依旧没有丢失,这就实现了容器数据持久化功能!
具名和匿名挂载
# 匿名挂载 -v 容器内路径
docker run -d -P --name nginx01 -v /etc/nginx nginx
# 具名挂载 -v 卷名:容器内路径
docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx nginx
# 查看所有卷的情况
docker volume ls
# 通过 -v 卷名:容器内路径
# 查看一下这个卷 # 先找到卷所在路径 docker volume inspect 卷名,如下图:
拓展:
# 通过 -v 容器内路径:ro 或 rw 改变读写权限
ro #readonly 只读
rw #readwrite 可读可写
# 一旦创建容器时设置了容器权限,容器对我们挂载出来的内容就有限定了!
docker run -d -P --name nginx05 -v juming:/etc/nginx:ro nginx
docker run -d -P --name nginx05 -v juming:/etc/nginx:rw nginx
# 默认是 rw
# ro 只要看到ro就说明这个路径只能通过宿主机来操作,容器内部是无法操作!
数据卷容器
docker run -it --name container02 --volumes from container01 镜像名/id # 将两个容器进行挂载
多个mysql实现数据共享同步
docker run -d -p 3306:3306 -v /home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql:5.7
docker run -d -p 3307:3306 -e MYSQL_ROOT_PASSWORD=123456 --name mysql02 --volumes-from mysql01 mysql:5.7
# 这个时候,可以实现两个容器数据同步!
DockerFile
DockerFile介绍
dockerfile是用来构建docker镜像的文件!命令参数脚本!
构建步骤:
docker build -f 文件路径 -t 镜像名 . # 文件名为Dockerfile时可省略且最后的.不要忽略
docker run # 运行镜像
docker push # 发布镜像
举列:
[root@iZ1608aqb7ntn9Z 20210806]# vim Dockerfile
# ----------写入内容-----------------
FROM centos # 来自centos
CMD /bin/bash # 进入到/bin/bash
CMD echo Hello Dockerfile # 输出Hello Dockerfile
# ----------写入结束-----------------
[root@iZ1608aqb7ntn9Z 20210806]# docker build -f ./Dockerfile -t mydocker .
Sending build context to Docker daemon 2.56kB
Step 1/3 : FROM centos
---> 300e315adb2f
Step 2/3 : CMD /bin/bash
---> Running in 526f489adf0b
Removing intermediate container 526f489adf0b
---> 3c2af9c73098
Step 3/3 : CMD echo Hello Dockerfile
---> Running in 023af54a93e2
Removing intermediate container 023af54a93e2
---> 7753b44c9137
Successfully built 7753b44c9137
Successfully tagged mydocker:latest
[root@iZ1608aqb7ntn9Z 20210806]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mydocker latest 7753b44c9137 6 seconds ago 209MB
......
[root@iZ1608aqb7ntn9Z 20210806]# docker run -it mydocker
Hello Dockerfile
DockerFile的指令
| 指令 | 释义 |
|---|---|
| FROM | 基础镜像:Centos/Ubuntu |
| MAINTAINER | 作者 |
| RUN | 镜像构建的时候需要运行的命令 |
| ADD | 为镜像添加内容(压缩包) |
| WORKDIR | 镜像工作目录(进入容器时的目录) |
| VOLUME | 挂载的目录 |
| EXPOSE | 暴露端口配置 |
| CMD/ENTRYPOINT | 指定这个容器启动时要运行的命令(CMD替代先前命令,ENTRYPOINT在先前命令后追加) |
| COPY | 类似于ADD,将文件拷贝到镜像中 |
| ENV | 构建时设置环境变量 |
实战:jdk+Tomcat镜像
1、准备镜像文件tomcat压缩包,jdk压缩包!
2、编写Dockerfile文件,官方命名: Dockerfile ,build会自动寻找这个文件,就不要 -f 指定了!
FROM centos
MAINTAINER name<email>
COPY readme.txt /usr/local/readme.txt
ADD jdk-8u161-linux-x64.tar.gz /usr/local/
ADD apache-tomcat-8.0.53.tar.gz /usr/local
RUN yum -y install vim
ENV MYPATH /usr/local
WORKDIR $MYPATH
ENV JAVA_HOME /usr/local/jdk1.8.0_161
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV CATALINA_HOME /usr/local/apache-tomcat-8.0.53
ENV CATALINA_BASH /usr/local/apache-tomcat-8.0.53
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin
EXPOSE 8080
CMD /usr/local/apache-tomcat-8.0.53/bin/startup.sh && tail -F /usr/local/apache-tomcat-8.0.53/bin/logs/catalina.out
3、构建镜像
# docker build -t diytomcat . diytomcat是定义的镜像名
4、启动镜像,创建容器
# docker run -d -p 9090:8080 --name mytomcat -v /home/service/build/tomcat/test:/usr/local/apache-tomcat-8.0.53/webapps/test -v /home/service/build/tomcat/tomcatlogs/:/usr/local/apache-tomcat-8.0.53/logs diytomcat
5、访问测试
6、发布项目(由于做了卷挂载,我们就可以直接在本地发布项目了)
在/home/service/build/tomcat/test目录下创建WEB-INF目录,在里面创建web.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
</web-app>
在回到test目录,添加一个index.jsp页面:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>hello-world</title>vim
</head>
<body>
Hello World!<br/>
<%
System.out.println("---test web---");
%>
</body>
</html>
7.访问测试:ip:port/project
docker push发布镜像
Docker Hub
在我们服务器上提交自己的镜像 1.登录DockerHub账号
docker login -u username
Password:
4、登录完毕后就可以提交镜像了,就是一步 docker push
# push自己的镜像到服务器上!
docker push username/imagename:version
# push镜像的问题?
# 解决:增加一个tag docker tag 指定镜像的id dockerhub的用户名/镜像重命名:[tag]
docker tag bb64ab96b432 username/imagename:version
#本地出现多个相同id的镜像
docker rmi -f 镜像名:tag
注意:镜像的重命名前一定要加当前的dockerhub的用户名,否则将会push失败!!!!
小结
Docker网络
理解Docker0
ip address #查看本地ip地址,发现有多个网卡
问题: docker是如何处理容器网络访问的?
# [root@localhost /]# docker run -d -P --name tomcat01 tomcat
# 查看容器的内部网络地址 ip addr, 发现容器启动的时候会得到一个 eth0@if43 ip地址,docker分配的!
[root@localhost /]# docker exec -it tomcat01 ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
42: eth0@if43: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
# 思考:linux能不能ping通docker容器内部!
[root@localhost /]# ping 172.17.0.2
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.476 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.099 ms
64 bytes from 172.17.0.2: icmp_seq=3 ttl=64 time=0.105 ms
...
# linux 可以ping通docker容器内部
原理
1、我们每启动一个docker容器,docker就会给docker容器分配一个ip,我们只要装了docker,就会有一个docker01网卡。
桥接模式,使用的技术是veth-pair技术!
再次测试 ip addr,发现多了一对网卡 :
2、再启动一个容器测试,发现又多了一对网卡!!!
# 我们发现这个容器带来网卡,都是一对对的
# veth-pair 就是一对的虚拟设备接口,他们都是成对出现的,一段连着协议,一段彼此相连
# 正因为有这个特性,veth-pair 充当一个桥梁,连接各种虚拟网络设备
# OpenStack,Docker容器之间的连接,OVS的连接都是使用veth-pair技术
3、我们来测试下tomcat01和tomcat02是否可以ping通!
[root@localhost /]# docker exec -it tomcat02 ping 172.17.0.2
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.556 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.096 ms
64 bytes from 172.17.0.2: icmp_seq=3 ttl=64 time=0.111 ms
...
# 结论:容器与容器之间是可以相互ping通的!!!
网络模型图:
结论:tomcat01 和 tomcat02 是公用一个路由器,即 docker0 !
所有的容器不指定网络的情况下,都是经 docker0 路由的,docker 会给我们的容器分配一个默认的可用ip
小结
Docker使用的是Linux的桥接技术,宿主机是一个Docker容器的网桥 docker0
注意: Docker中所有网络接口都是虚拟的,虚拟的转发效率高!(内网传递文件)
只要容器一删除,对应的一对网桥就没有!
--link
思考一个场景:我们编写了一个微服务,database url = ip ,项目不重启,数据库ip换掉了,我们希望可以处理这个问题,可以通过名字来访问容器?
# tomcat02 想通过直接ping 容器名(即"tomcat01")来ping通,而不是ip,发现失败了!
[root@localhost /]# docker exec -it tomcat02 ping tomcat01
ping: tomcat01: Name or service not known
# 如何解决这个问题呢?
# 通过--link 就可以解决这个网络联通问题了!!! 发现新建的tomcat03可以ping通tomcat02
[root@localhost /]# docker run -d -P --name tomcat03 --link tomcat02 tomcat
87a0e5f5e6da34a7f043ff6210b57f92f40b24d0d4558462e7746b2e19902721
[root@localhost /]# docker exec -it tomcat03 ping tomcat02
PING tomcat02 (172.17.0.3) 56(84) bytes of data.
64 bytes from tomcat02 (172.17.0.3): icmp_seq=1 ttl=64 time=0.132 ms
64 bytes from tomcat02 (172.17.0.3): icmp_seq=2 ttl=64 time=0.116 ms
64 bytes from tomcat02 (172.17.0.3): icmp_seq=3 ttl=64 time=0.116 ms
64 bytes from tomcat02 (172.17.0.3): icmp_seq=4 ttl=64 time=0.116 ms
# 反向能ping通吗? 发现tomcat02不能oing通tomcat03
[root@localhost /]# docker exec -it tomcat02 ping tomcat03
ping: tomcat03: Name or service not known
探究:inspect !!!
其实这个tomcat03就是在本地配置了到tomcat02的映射:
# 查看hosts 配置,在这里发现原理!
[root@localhost /]# docker exec -it tomcat03 cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.3 tomcat02 95303c12f6d9 # 就像windows中的 host 文件一样,做了地址绑定
172.17.0.4 87a0e5f5e6da
本质探究:--link 就是我们在hosts 配置中增加了一个 172.17.0.3 tomcat02 95303c12f6d9 (三条信息都是tomcat02 的)
我们现在玩Docker已经不建议使用 --link 了!!!
自定义网络,不使用docker0!
docker0问题:不支持容器名连接访问!
--net 自定义网络
docker network ls # 查看所有的docker网络
网络模式
bridge :桥接(docker默认,自己创建也使用bridge模式!)
none :不配置网络
host :和宿主机共享网络
container :容器网络连通,容器直接互联!(用的少!局限很大!)
测试
# 我们之前直接启动的命令 (默认是使用--net bridge,可省),这个bridge就是我们的docker0
docker run -d -P --name tomcat01 tomcat
docker run -d -P --name tomcat01 --net bridge tomcat
# 上面两句等价
# docker0(即bridge)默认不支持域名访问 ! --link可以打通连接,即支持域名访问!
# 我们可以自定义一个网络!
# --driver bridge 网络模式定义为 :桥接
# --subnet 192.168.0.0/16 定义子网 ,范围为:192.168.0.2 ~ 192.168.255.255
# --gateway 192.168.0.1 子网网关设为: 192.168.0.1
[root@localhost /]# docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet
7ee3adf259c8c3d86fce6fd2c2c9f85df94e6e57c2dce5449e69a5b024efc28c
[root@localhost /]# docker network ls
NETWORK ID NAME DRIVER SCOPE
461bf576946c bridge bridge local
c501704cf28e host host local
7ee3adf259c8 mynet bridge local #自定义的网络
9354fbcc160f none null local
自己的网络就创建好了:
[root@localhost /]# docker run -d -P --name tomcat-net-01 --net mynet tomcat
b168a37d31fcdc2ff172fd969e4de6de731adf53a2960eeae3dd9c24e14fac67
[root@localhost /]# docker run -d -P --name tomcat-net-02 --net mynet tomcat
c07d634e17152ca27e318c6fcf6c02e937e6d5e7a1631676a39166049a44c03c
[root@localhost /]# docker network inspect mynet
[
{
"Name": "mynet",
"Id": "7ee3adf259c8c3d86fce6fd2c2c9f85df94e6e57c2dce5449e69a5b024efc28c",
"Created": "2020-06-14T01:03:53.767960765+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "192.168.0.0/16",
"Gateway": "192.168.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"b168a37d31fcdc2ff172fd969e4de6de731adf53a2960eeae3dd9c24e14fac67": {
"Name": "tomcat-net-01",
"EndpointID": "f0af1c33fc5d47031650d07d5bc769e0333da0989f73f4503140151d0e13f789",
"MacAddress": "02:42:c0:a8:00:02",
"IPv4Address": "192.168.0.2/16",
"IPv6Address": ""
},
"c07d634e17152ca27e318c6fcf6c02e937e6d5e7a1631676a39166049a44c03c": {
"Name": "tomcat-net-02",
"EndpointID": "ba114b9bd5f3b75983097aa82f71678653619733efc1835db857b3862e744fbc",
"MacAddress": "02:42:c0:a8:00:03",
"IPv4Address": "192.168.0.3/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]
# 再次测试 ping 连接
[root@localhost /]# docker exec -it tomcat-net-01 ping 192.168.0.3
PING 192.168.0.3 (192.168.0.3) 56(84) bytes of data.
64 bytes from 192.168.0.3: icmp_seq=1 ttl=64 time=0.199 ms
64 bytes from 192.168.0.3: icmp_seq=2 ttl=64 time=0.121 ms
^C
--- 192.168.0.3 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 2ms
rtt min/avg/max/mdev = 0.121/0.160/0.199/0.039 ms
# 现在不使用 --link,也可以ping 名字了!!!!!!
[root@localhost /]# docker exec -it tomcat-net-01 ping tomcat-net-02
PING tomcat-net-02 (192.168.0.3) 56(84) bytes of data.
64 bytes from tomcat-net-02.mynet (192.168.0.3): icmp_seq=1 ttl=64 time=0.145 ms
64 bytes from tomcat-net-02.mynet (192.168.0.3): icmp_seq=2 ttl=64 time=0.117 ms
^C
--- tomcat-net-02 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 3ms
rtt min/avg/max/mdev = 0.117/0.131/0.145/0.014 ms
我们在使用自定义的网络时,docker都已经帮我们维护好了对应关系,推荐我们平时这样使用网络!!!
好处:
redis——不同的集群使用不同的网络,保证了集群的安全和健康
mysql——不同的集群使用不同的网络,保证了集群的安全和健康
network connect网络连通
# 测试打通 tomcat01 — mynet
[root@localhost /]# docker network connect mynet tomcat01
# 连通之后就是将 tomcat01 放到了 mynet 网络下! (见下图)
# 这就产生了 一个容器有两个ip地址 ! 参考阿里云的公有ip和私有ip
[root@localhost /]# docker network inspect mynet
# tomcat01 连通ok
[root@localhost /]# docker exec -it tomcat01 ping tomcat-net-01
PING tomcat-net-01 (192.168.0.2) 56(84) bytes of data.
64 bytes from tomcat-net-01.mynet (192.168.0.2): icmp_seq=1 ttl=64 time=0.124 ms
64 bytes from tomcat-net-01.mynet (192.168.0.2): icmp_seq=2 ttl=64 time=0.162 ms
64 bytes from tomcat-net-01.mynet (192.168.0.2): icmp_seq=3 ttl=64 time=0.107 ms
^C
--- tomcat-net-01 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 3ms
rtt min/avg/max/mdev = 0.107/0.131/0.162/0.023 ms
# tomcat02 是依旧打不通的
[root@localhost /]# docker exec -it tomcat02 ping tomcat-net-01
ping: tomcat-net-01: Name or service not known
结论: 假设要跨网络操作别人,就需要使用docker network connect 连通。。。
Springboot微服务打包Docker镜像
打包运行
mvn package
编写Dockerfile
FROM java:8
COPY *.jar /app.jar
CMD ["--server.port=8080"]
EXPOSE 8080
ENTRYPOINT ["java","-jar","app.jar"]
构建镜像
# 1.复制jar和DockerFIle到服务器
# 2.构建镜像
docker build -t xxxxx:xx .