作为一名程序员,无论是开发、测试还是运维,对虚拟机再熟悉不过了,比如常用的VMware属于虚拟机的一种,除此之外还包括VisualBox、Virtual PC等等。虚拟机是具有完整硬件系统功能,运行在完全隔离环境的计算机系统。
虚拟机的优势:在一台物理机上通过虚拟化技术虚拟出多个独立的Guest os。每一个Guest os拥有独立的硬件资源。
缺点:虚拟机占用资源比较多,每个虚拟机都需要os运行的所有硬件资源的虚拟副本。而且有由于添加了一层Hypervisor虚拟化技术,效率也比较低。
大多开发人员都曾经遇到过这种问题:应用部署到本地测试环境没问题,一上生产就各种问题,因为服务器环境、配置的不同排查问题也比较困难。
针对以上问题,加上应用越来越多,给运维带来了很大的工作量,容器时代来临了。容器比虚拟机更轻量,但功能应有尽有。
虚拟机通过Hypervisor对硬件进行虚拟化,容器是直接操作硬件资源,所以容器对资源利用率较高,运行速度更快。同时对于应用需要创建Guest os,而容器只需要启动一个镜像即可。
容器相对于虚拟机:
1)容器启动较快,属于秒级别,虚拟机一般需求几分钟;
2)容器直接与内核交互,占用资源少,效率更快;
3)虚拟机隔离性比较好,所以通常是虚拟机和容器公用;
4)容器在部署,特别是云原生中能够快速交付、快速迭代;
docker包括镜像、容器、仓库三大部分:
镜像是一个只读联合文件系统,包括容器运行所需的程序、资源、环境、配置。镜像具有分层概念,是将不同层组合成一个文件系统。
下图是一个tomcat镜像,最底层bootfs(boot-file system)包含bootloader和kernel,bootloader主要是加载kernel到内存中,操作系统启动时会加载bootfs文件系统,加载完成后,内存使用权由bootfs交给kernel,然后由系统卸载bootfs。
rootfs基础镜像是在bootfs之上,是不同操作系统的发行版,比如centos、ubuntu;rootfs是精简版的操作系统,所以占用资源比较小。
因为tomcat还依赖于jdk,所以基础镜像上面还需要添加jdk镜像,然后才是tomcat镜像。每一层可以全局公用,比如你本地加载了tomcat镜像,如果再使用jdk无需重新加载,直接使用本地已加载的即可。注意镜像是由镜像名:标签组成,如果不同的标签即不同的版本镜像是不一样的。
容器:在镜像上面添加一层可读可写层,如上图,相当于容器=镜像+可读可写层。
仓库:类似Git一样,docker也包括远程仓库和本地仓库,主要是存储镜像。本地构建的镜像也可以push到远程仓库中。
docker是C/S结构,用户通过客户端操作docker,docker服务器负载对镜像、容器的操作,客户端可以和服务器部署在一起,也可以通过restful、网络接口与远程docker服务器进行通信交互。
1)卸载docker Engine, CLI, 和容器包
$ sudo yum remove docker-ce docker-ce-cli containerd.io
2)删除镜像、容器以及卷
$ sudo rm -rf /var/lib/docker
$ sudo rm -rf /var/lib/containerd
1)卸载旧版本
$ sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
2)安装需求的软件包
$ sudo yum install -y yum-utils
3)设置镜像仓库
$ sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
建议用国内的
$ sudo yum-config-manager \
--add-repo \
http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
4)更新yum软件包,如果是centos8去掉fast
$ yum makecache fast
5)安装Docker CE
$ sudo yum install docker-ce docker-ce-cli containerd.io
6)启动镜像,如果是centos8执行下面命令报错,需要先执行yum erase podman buildah
$ sudo systemctl start(stop、restart) docker
6)测试安装结果
$ docker version
Client: Docker Engine - Community
Version: 20.10.7
API version: 1.41
Go version: go1.13.15
Git commit: f0df350
Built: Wed Jun 2 11:56:47 2021
OS/Arch: linux/amd64
Context: default
Experimental: true
Server: Docker Engine - Community
Engine:
Version: 20.10.7
API version: 1.41 (minimum version 1.12)
Go version: go1.13.15
Git commit: b0f5bc3
Built: Wed Jun 2 11:54:58 2021
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.4.6
GitCommit: d71fcd7d8303cbf684402823e425e9dd2e99285d
runc:
Version: 1.0.0-rc95
GitCommit: b9ee9c6314599f1b4a7f497e1f1f856fe433d3b7
docker-init:
Version: 0.19.0
GitCommit: de40ad0
7)配置镜像加速器
登录阿里云官网找到容器镜像服务,每个用户都有自己的加速器地址
万能命令docker --help,所有的docker命令都可以通过--help查看。
查看本地镜像信息
root@review:~# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mysql 5.7 938b57d64674 2 weeks ago 448MB
搜索远程镜像
root@review:~# docker search mysql
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
mysql MySQL is a widely used, open-source relation… 11616 [OK]
下载远程镜像
root@review:~# docker pull mysql
Using default tag: latest
latest: Pulling from library/mysql
b380bbd43752: Already exists
f23cbf2ecc5d: Already exists
30cfc6c29c0a: Already exists
b38609286cbe: Already exists
8211d9e66cd6: Already exists
2313f9eeca4a: Already exists
7eb487d00da0: Already exists
4d7421c8152e: Pull complete
77f3d8811a28: Pull complete
cce755338cba: Pull complete
69b753046b9f: Pull complete
b2e64b0ab53c: Pull complete
Digest: sha256:6d7d4524463fe6e2b893ffc2b89543c81dec7ef82fb2020a1b27606666464d87
Status: Downloaded newer image for mysql:latest
docker.io/library/mysql:latest
删除本地镜像
root@review:~# docker rmi mysql:latest
Untagged: mysql:latest
Untagged: mysql@sha256:6d7d4524463fe6e2b893ffc2b89543c81dec7ef82fb2020a1b27606666464d87
Deleted: sha256:ecac195d15afac2335de52fd7a0e34202fe582731963d31830f1b97700bf9509
Deleted: sha256:451fe04d80b84c0b7aca0f0bbdaa5de7c7ac85a65389ed5d3ed492f63ac092e2
Deleted: sha256:814cbf8bc7f6bb85685e5b803e16a76406c30d1960c566eee76303ffac600600
Deleted: sha256:735f72e1d1b936bb641b6a1283e4e60bf10a0c36f8244a5e3f8c7d58fa95b98a
Deleted: sha256:f2d209a30c3950fadffb2d82e1faa434da0753bee7aacad9cdec7d8a7a83df37
Deleted: sha256:03b9f8c5331d9534d2372a144bcffc8402e5f7972c9e4b85c634bef203ec6d20
我们可以通过save命令将镜像打包成文件,拷贝给别人使用
docker save -o 保存的文件名 镜像名 eg:docker save -o ./ubuntu.tar ubuntu
在拿到镜像文件后,可以通过load方法,将镜像加载到本地
docker load -i ./ubuntu.tar
启动容器:docker run为启动命令,
-d:后台运行
--name:容器名
-h:进入容器后主机名
--restart always:docker容器重启后,该容器自动启动
-e:设置环境变量
-v:设置数据卷
-p:对外暴露端口
mysql:8:镜像名
$ docker run -d --name mysql-test -h mysql-test --restart always
-e MYSQL_ROOT_PASSWORD=tiger -e MYSQL_DATABASE='test'
-v /app/mysql/conf/my.cnf:/etc/mysql/my.cnf -v /data/:/data/
-v /data/mysql/data:/var/lib/mysql
-v /data/mysql/mysql-files:/var/lib/mysql-files/
-p 3306:3306 mysql:8
查看容器信息:包括数据挂载、网络、容器其他信息
root@review:~# docker inspect kafka
[
{
"Id": "38c54e6e0b5d371afcbb4cf22cea4401f577297668ec5f2ddee0162b01bd0e36",
"Created": "2021-11-01T07:31:53.437447759Z",
"Path": "/opt/bitnami/scripts/kafka/entrypoint.sh",
"Args": [
"/opt/bitnami/scripts/kafka/run.sh"
],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 3450,
"ExitCode": 0,
"Error": "",
"StartedAt": "2021-11-01T07:31:55.149049019Z",
"FinishedAt": "0001-01-01T00:00:00Z"
docker exec进入一个正在运行的容器
$ docker exec -it 容器名/id /bin/bash(sh)
-i:即使没有连接,也要保持STDIN打开,输入命令后程序会卡住如下图
但是可以在上面输入查询命令
-t:分配一个伪终端,执行下面命令会进入容器中,
在容器中输入命令没有任何反应
输入完整命令后
除了docker exec,docker attach也可以进入容器
docker exec和docker attach区别:docker exec在容器内会起一个新的进程,退出的时候不会关闭容器;docker attach直接进入容器启动终端,如果你从两个终端attach到一个container,当你在一个终端输入的时候,内容会出现在另一个终端,两个终端是连接在同一个tty上,所以直接退出的时候会将容器一起关闭,如果想不关闭容器,可以Ctrl+P+Q。
同时,docker run -d mysql启动容器的驻守程序是sshd,docker attact截取的输入输出是该进程(/user/sbin/sshd -D)的。这个进程是不接受输入的,用户无法直接进入容器,所以程序会卡住。
删除容器,rm:删除容器,-f:强制执行
root@review:~# docker rm -f kafka-manager
kafka-manager
启动容器后,如果修改容器中内容可通过docker commit -a="作者" -m="提交说明" 容器id 镜像名:版本,生成新的镜像。
docker容器如果关闭,则这个容器被kill掉,容器内的数据一并被清空,为了防止数据丢失,docker通过使用数据卷将数据持久化到宿主机中。
数据卷包括具名数据卷和匿名数据卷,匿名数据卷可以通过docker inspect查看具体位置
具名数据卷: docker run -v 名称(不是以/开头):容器路径:ro/rw(ro只读,rw可读可写)
匿名数据卷:docker run -v 容器路径
可以通过docker volume inspect 数据卷名 查看挂载路径
root@review:/app# docker volume inspect zk
[
{
"CreatedAt": "2021-11-02T19:20:12+08:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/zk/_data",
"Name": "zk",
"Options": null,
"Scope": "local"
}
]
数据卷挂载几种方式:
1)通过docker run -v /宿主机路径:/容器路径
2)通过Dockerfile VOLUME
3)数据卷容器 --volume-from
B容器 --volume-from A容器(A称为数据卷容器,类似于B继承于A),只要有一个容器在,数据就不会丢失
Dockerfile是自动构建镜像的配置文件,简单的讲可以通过Dockerfile文件构建自己的docker镜像,Dockerfile文件是由一组命令组成,命令必须要大写,#为注释。
Dockerfile常用命令:
# 基础镜像,常用的任何镜像都需要一个基础镜像,tag不选,默认为latest
FROM 镜像名:TAG
# 作者信息
MAINTAINER mian [email protected]
# 将本地文件添加到容器中,具有自动解压功能,可以访问网络资源,类似wget
ADD app.jar /
# 功能类似ADD,但是不能自动解压文件,也不能访问网络资源
COPY app.jar /
# 构建镜像时执行的命令
RUN ["yum", "install", "-y", "gcc", "gcc-c++"]
# 设置环境变量
ENV PATH /usr/local/nginx/sbin:$PATH
# 对外暴露端口,可以同时指定多个(只是说明docker容器开放了哪些端口,并没有将这些端口实际开放了出来,需要docker run -p指定)
EXPOSE 8080 443
# 进入容器后指定工作目录,类似于cd
WORKDIR /app
# 设置数据卷,将容器中的数据持久化到本地宿主机
VOLUME ["/opt", "/app"]
# 容器启动的时候执行
CMD ["java", "-jar", "app.jar"]
# 容器启动时执行
ENTRYPOINT ["top"]
ENTRYPOINT与CMD:
相同点:Dockerfile文件只会保留最后一条命令;
不同点:docker run执行的命令不会覆盖ENTRYPOINT,而docker run命令中指定的任何参数,都会被当做参数再次传递给ENTRYPOINT。docker run后面的命令会覆盖CMD中的命令。
通过docker build构建镜像如果在当前目录后面直接加.
$ docker build -f .(Dockerfile文件的绝对路径)
docker0是docker容器自带的网卡,默认为桥接模式,每开启一个容器都会自动生成一个随机ip。
桥接模式使用的evth-pair技术:就是一对虚拟设备接口,每开启一个容器,会产生一对网卡,一段连接协议,一段彼此相连,正因为这个特性,evth-pair充当一个桥梁,可以连接各种虚拟网络设备。
容器tomcat01和tomcat02之间通过ip是可以ping通的
外部访问docker容器网络如下图,通过宿主机网卡直连到docker0网桥,然后访问到具体的应用容器。
当容器关闭后,网桥也随之关闭。
由于docker分配的ip都是随机的,需要一个类似不变的服务名代替,如果直接通过服务名是ping不通的,需要添加--link :单向通信,反向是无效的
# 没加--link之前
root@08f58591fa91:/usr/local/tomcat# ping tomcat02
ping: unknown host tomcat02
# 添加--link之后
root@review:~# docker run -d --link tomcat02 --name tomcat01 -p 8085:8080 tomcat:8.0
7308f12982f786796e519e70af48194ef7a04a7915ad092728b292227320ba2b
root@7308f12982f7:/usr/local/tomcat# ping tomcat02
PING tomcat02 (172.17.0.4) 56(84) bytes of data.
64 bytes from tomcat02 (172.17.0.4): icmp_seq=1 ttl=64 time=0.239 ms
64 bytes from tomcat02 (172.17.0.4): icmp_seq=2 ttl=64 time=0.116 ms
64 bytes from tomcat02 (172.17.0.4): icmp_seq=3 ttl=64 time=0.141 ms
除了上面方式外,可以通过自定义网络进行联通
docker network ls
root@review:~# docker network create (--driver bridge) --subnet 192.168.1.0/16 --gateway 192.168.1.1 mynet
d6db3b4e7005a0339988383be574f9fa2c44cdd7a7c19bc4582c91c89c61cdeb
root@review:~# docker network ls
NETWORK ID NAME DRIVER SCOPE
70efc8679b96 bridge bridge local
d3bfd3884747 compose_default bridge local
f07f88b80132 host host local
d6db3b4e7005 mynet bridge local
72827e9e4d40 none null local
--driver:网络模式,如果不写默认是网桥模式
--subnet:子网地址
--gateway:网关地址
$ docker run -d --name my-nginx --net mynet nginx
docker network connect 网络名 容器名,执行完后相当于在容器中添加一个网络配置
创建后,只要在这个网络内的都可以通过服务名进行ping通
root@099b2259faaa:/usr/local/tomcat# ping tomcat02
PING tomcat02 (192.168.0.2) 56(84) bytes of data.
64 bytes from tomcat02.mynet (192.168.0.2): icmp_seq=1 ttl=64 time=0.123 ms
64 bytes from tomcat02.mynet (192.168.0.2): icmp_seq=2 ttl=64 time=0.057 ms
64 bytes from tomcat02.mynet (192.168.0.2): icmp_seq=3 ttl=64 time=0.087 ms
root@659f472e242c:/usr/local/tomcat# ping tomcat01
PING tomcat01 (192.168.0.1) 56(84) bytes of data.
64 bytes from tomcat01.mynet (192.168.0.1): icmp_seq=1 ttl=64 time=0.049 ms
64 bytes from tomcat01.mynet (192.168.0.1): icmp_seq=2 ttl=64 time=0.064 ms
64 bytes from tomcat01.mynet (192.168.0.1): icmp_seq=3 ttl=64 time=0.057 ms
参考文献:【狂神说Java】Docker最新超详细版教程通俗易懂_哔哩哔哩_bilibili