缺点部署慢成本高一个物理机上安装操作系统,然后直接运行我们的软件。也就是说你电脑上直接跑了一个软件,并没有开虚拟机什么的,资源极其浪费。
资源浪费虚拟机都不开,直接上物理机部署应用,那成本能不高嘛
不易迁移和增加机器硬件资源没有合理利用
受限于硬件虚拟机时代每次迁移都要重新安装一模一样的运行环境等
特点多部署一个物理机上虚拟化出来多个虚拟机,每个虚拟机上进行安装操作系统,然后在虚拟机上直接运行我们的软件。比如阿里云、腾讯云等云服务器都是虚拟机。
资源隔离一个物理机上创建多个虚拟机,每个虚拟机的ip都不同,一个软件可以在不同虚拟机上进行部署。
易扩展每个虚拟机都由独立分配的内存磁盘等硬件资源,通过Hypervisor分配。
成本相对较低缺点虚拟机需要安装操作系统随时随地都能在一个物理机上开出新的虚拟机。成本也低。
容器化时代假设我们的应用程序只有几KB,或者几十MB,但是却要在虚拟机上安装一个几百兆的操作系统来跑。
特点标准化的迁移方式虚拟化技术是物理硬件的隔离,容器化技术是app层面的隔离,两者不冲突。往往容器化时代都是基于虚拟机基础之上做的资源隔离。它可以基于虚拟机的操作系统部署多服务,不再需要额外安装操作系。
统一的参数配置自动化部署应用集群监控开发与运维之间的沟通桥梁传统虚拟机和Docker的比较2、Docker是什么开发者将环境和程序打包给运维,运维直接执行就完事了。
docker官方地址:https://www.docker.com/
Docker is an open platform for developing, shipping, and running applications. Docker enables you to separate your applications from your infrastructure so you can deliver software quickly. With Docker, you can manage your infrastructure in the same ways you manage your applications. By taking advantage of Docker’s methodologies for shipping, testing, and deploying code quickly, you can significantly reduce the delay between writing code and running it in production.
Docker 最初是 dotCloud 公司创始人 Solomon Hykes 在法国期间发起的一个公司内部项目,于 2013 年 3 月以 Apache 2.0 授权协议开源,主要项目代码在 GitHub 上进行维护。 Docker 使用 Google 公司推出的 Go 语言 进行开发实现。 ? Docker 是linux容器的一种封装,提供简单易用的容器使用接口。它是最流行的Linux容器解决方案。 ? Docker 的接口相当简单,用户可以方便的创建、销毁容器。 ? Docker 将应用程序与程序的依赖,打包在一个文件里面。运行这个文件就会生成一个虚拟容器。程序运行在虚拟容器里,如同在真实物理机上运行一样,有了Docker ,就不用担心环境问题了。
Docker conceptsDocker is a platform for developers and sysadmins to build, run, and share applications with containers. The use of containers to deploy applications is called containerization. Containers are not new, but their use for easily deploying applications is.
Containerization is increasingly popular because containers are:
Flexible: Even the most complex applications can be containerized.灵活:即使是最复杂的应用也可以集装箱化。Lightweight: Containers leverage and share the host kernel, making them much more efficient in terms of system resources than virtual machines.轻量级:容器利用并共享主机内核,使它们在系统资源方面比虚拟机效率更高Portable: You can build locally, deploy to the cloud, and run anywhere.便携式:您可以在本地构建,部署到云,并在任何地方运行Loosely coupled: Containers are highly self sufficient and encapsulated, allowing you to replace or upgrade one without disrupting others.松耦合:容器是高度自给自足和封装的,允许您更换或升级一个容器,而不会中断其他容器。Scalable: You can increase and automatically distribute container replicas across a datacenter.可扩展:您可以在数据中心增加并自动分发容器副本。Secure: Containers apply aggressive constraints and isolations to processes without any configuration required on the part of the user.安全:容器对进程应用严格的约束和隔离,而不需要用户进行任何配置。以上引自docker官方文档:https://docs.docker.com/get-started/3、Docker应用场景web应用的自动化打包和发布自动化测试和持续集成、发布在服务型环境中部署和调整数据库或其他应用4、Docker三大概念Docker架构
镜像 imagedocker镜像就是一个只读模板,比如,一个镜像可以包含一个完整的centos,里面仅安装apache或用户的其他应用,镜像可以用来创建docker容器,另外docker提供了一个很简单的机制来创建镜像或者更新现有的镜像,用户甚至可以直接从其他人那里下载一个已经做好的镜像来直接使用。容器 containerdocker利用容器来运行应用,容器是从镜像创建的运行实例,它可以被启动,开始、停止、删除、每个容器都是互相隔离的,保证安全的平台,可以把容器看做是简易版的linux环境(包括root用户权限、镜像空间、用户空间和网络空间等)和运行在其中的应用程序。仓库 repository镜像构建完成后,可以很容易的在当前宿主机上运行,但是,如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry就是这样的服务。 一个Docker Registry中可以包含多个仓库( Repository) ; 每个仓库可以包含多个标签(Tag) ;每个标签对应一个镜像。通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过<仓库名>:<标签>的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以latest作为默认标签。仓库分为两种,公有仓库和私有仓库,最大的公共仓库是docker Hub,存放了数量庞大的镜像供用户下载,国内的docker pool,这里仓库的概念与Git类似,registry可以理解为github这样的托管服务。5、Docker优势# 1. 更高效的利用系统资源由于容器不需要进行硬件虚拟以及运行完整操作系统等额外开销,Docker 对系统 资源的利用率更高。无论是应用执行速度、内存损耗或者文件存储速度,都要比传 统虚拟机技术更高效。因此,相比虚拟机技术,一个相同配置的主机,往往可以运 行更多数量的应用。# 2. 更快速的启动时间传统的虚拟机技术启动应用服务往往需要数分钟,而 Docker 容器应用,由于直接 运行于宿主内核,无需启动完整的操作系统,因此可以做到秒级、甚至毫秒级的启 动时间。大大的节约了开发、测试、部署的时间。# 3. 一致的运行环境开发过程中一个常见的问题是环境一致性问题。由于开发环境、测试环境、生产环 境不一致,导致有些 bug 并未在开发过程中被发现。而 Docker 的镜像提供了除内 核外完整的运行时环境,确保了应用运行环境一致性,从而不会再出现 “这段代码 在我机器上没问题啊” 这类问题。# 4. 持续交付和部署对开发和运维(DevOps)人员来说,最希望的就是一次创建或配置,可以在任意 地方正常运行。使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。开发人员 可以通过 Dockerfile 来进行镜像构建,并结合 持续集成(Continuous Integration) 系 统进行集成测试,而运维人员则可以直接在生产环境中快速部署该镜像,甚至结合 持续部署(Continuous Delivery/Deployment) 系统进行自动部署。而且使用 Dockerfile 使镜像构建透明化,不仅仅开发团队可以理解应用运行环 境,也方便运维团队理解应用运行所需条件,帮助更好的生产环境中部署该镜像。# 5. 更轻松的迁移由于 Docker 确保了执行环境的一致性,使得应用的迁移更加容易。Docker 可以在 很多平台上运行,无论是物理机、虚拟机、公有云、私有云,甚至是笔记本,其运 行结果是一致的。因此用户可以很轻易的将在一个平台上运行的应用,迁移到另一 个平台上,而不用担心运行环境的变化导致应用无法正常运行的情况。
二、Docker安装1、 环境准备
#系统环境:docker最低支持centos7且在64位平台上,内核版本在3.10以上#版本:Docker Community Edition(CE)社区版,Enterprise Edition(EE) 企业版(包含了一些收费服务)
2、安装步骤step1:使用yum命令进行安装
老版本安装方式 $ yum install -y docker 安装的版本是1.13.1(2017-2-8更新),不推荐
新版本安装:Docker官方文档:https://docs.docker.com/engine/install/centos/ 步骤摘要:
#1.卸载旧版本$ sudo yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-engine#2.安装yum工具包包(-y 表示不询问 使用默认配置进行安装,等待提示安装完毕!)$ sudo yum install -y yum-utils#3.设置镜像的仓库(默认是从国外的,推荐使用国内的)$ sudo yum-config-manager \ --add-repo \ https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo#更新yum软件包索引(可省略)#yum makecache fast#4.安装docker相关的 docker-ce 社区版 而ee是企业版$ sudo yum install -y docker-ce docker-ce-cli containerd.io#5.启动docker服务$ sudo systemctl start docker#6. 使用docker version查看docker版本$ sudo docker version#7. 测试$ sudo docker run hello-world
step2:查看是否安装成功
yum list installed|grep docker
备注:命令为 查看yum安装完成文件列表 并 筛选docker安装信息 如下
step3:启动docker服务systemctl start docker
step4:查看是否启动成功
systemctl status docker
如果没有出现 active(running)说明启动失败
解决方法:修改docker文件,具体命令:vi /etc/sysconfig/docker,在--selinux-enabled后面添加=false。如下图所示:
电脑#再次执行systemctl start dockersystemctl status docker
Step5:查看docker版本
docker version
Step6:测试
docker run hello-world
A. 打开阿里云网址https://cr.console.aliyun.com
B. 使用支付宝或淘宝账号登录
C. 搜索容器镜像服务
D. 复制红色区域内容
电脑E. 新建文件夹 mkdir -p /etc/docker
F. 编辑文件 vim /etc/docker/daemon.json 把刚刚复制的内容写进去保存
G. 重启docker
sudo systemctl daemon-reloadsudo systemctl restart docker
方式二:腾讯云镜像加速
腾讯云为您提供如下 DockerHub 加速器,以方便您快速拉取 DockerHub 平台上的容器镜像。 ? 此加速器地址需在腾讯云云服务器中配置才可生效,请勿通过浏览器直接访问,请参照以下教程进行配置。
# 1.创建或修改 /etc/docker/daemon.json 文件,并写入以下内容:{ "registry-mirrors": [ "https://mirror.ccs.tencentyun.com" ]}# 2.依次执行以下命令,重新启动 Docker 服务。$ sudo systemctl daemon-reload$ sudo systemctl restart docker# Ubuntu16.04 请执行 sudo systemctl restart dockerd 命令
腾讯云容器服务官方地址:https://cloud.tencent.com/document/product/457/9113
方式三:DaoCloud 加速器简介:DaoCloud 加速器是广受欢迎的 Docker工具,解决了国内用户访问 Docker Hub 缓慢的问题。DaoCloud 加速器结合国内的 CDN 服务与协议层优化,成倍的提升了下载速度。
# 一条命令加速(记得重启docker)curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s 电脑 http://f1361db2.m.daocloud.io
注意:此时可能会导致启动失败,原因实在配置镜像加速时在配置文件中多加了一个",",删除重启即可。
三、Docker命令1、帮助命令docker version #显示docker的版本信息。docker info #显示docker的系统信息,包括镜像和容器的数量docker 命令 --help #帮助命令
2、镜像命令
docker images #查看所有本地主机上的镜像 可以使用docker image ls代替Usage: docker images [OPTIONS] [REPOSITORY[:TAG]] -q #只显示镜像id -a #列出本地所有镜像(含中间映像层) --digests #显示镜像的摘要信息 --no-trunc #显示镜像完整信息[root@VM_0_5_centos ~]# docker images -aREPOSITORY TAG IMAGE ID CREATED SIZEdocker.io/hello-world latest bf756fb1ae65 5 months ago 13.3 kB
docker search #搜索镜像Usage: docker search [OPTIONS] TERM-f#根据指定条件删选镜像[root@VM_0_5_centos ~]# docker search mysql -f stars=5000 #搜索MySQL镜像stars超过5000的INDEX NAME STARS docker.io docker.io/mysql 9623
docker pull #下载镜像 docker image pullUsage: docker pull [OPTIONS] NAME[:TAG|@DIGEST][root@VM_0_5_centos ~]# docker pull tomcat:8.0 #如果不指定tag,默认下载最新版本[root@VM_0_5_centos ~]# docker images -aREPOSITORY TAG IMAGE ID CREATED SIZEdocker.io/tomcat 9.0.36-jdk8-openjdk b79665757bae 4 days ago 530 MBdocker.io/tomcat latest 2eb5a120304e 4 days ago 647 MBdocker.io/hello-world latest bf756fb1ae65 5 months ago 13.3 kBdocker.io/tomcat 8.0 ef6a7c98d192 21 months ago 356 MB
docker rmi #删除镜像 docker image rmUsage: docker rmi [OPTIONS] IMAGE [IMAGE...]Remove one or more imagesOptions: -f, --force #强制删除[root@VM_0_5_centos ~]# docker rmi tomcat#删除最新版本的tomcat[root@VM_0_5_centos ~]# docker rmi ef6a7c98d192#根据镜像id删除指定的tomcat[root@VM_0_5_centos ~]# docker rmi -f $(docker images -aq) #强制删除全部的镜像
3、容器命令
[root@VM_0_5_centos ~]# docker container --helpUsage: docker container COMMANDManage containersOptions: --help Print usageCommands: attach Attach to a running container commit Create a new image from a container's changes cp Copy files/folders between a container and the local filesystem create Create a new container diff Inspect changes on a container's filesystem exec Run a command in a running container export Export a container's filesystem as a tar archive inspect Display detailed information on one or more containers kill Kill one or more running containers logs Fetch the logs of a container ls List containers pause Pause all processes within one or more containers port List port mappings or a specific mapping for the container prune Remove all stopped containers rename Rename a container restart Restart one or more containers rm Remove one or more containers run Run a command in a new container start Start one or more stopped containers stats Display a live stream of container(s) resource usage statistics stop Stop one or more running containers top Display the running processes of a container unpause Unpause all processes within one or more containers update Update configuration of one or more containers wait Block until one or more containers stop, then print their exit codesRun 'docker container COMMAND --help' for more information on a command.
启动容器
docker run 镜像id #新建容器并启动Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]Options: -d, --detach Run container in background and print -e, --env list Set environment variables (default []) -i, --interactive Keep STDIN open even if not attached -p, --publish list Publish a container's port(s) to the host (default []) -P, --publish-all Publish all exposed ports to random ports --rm Automatically remove the container when it exits -t, --tty Allocate a pseudo-TTY -v, --volume list Bind mount a volume (default []) --volume-driver string Optional volume driver for the container --volumes-from list Mount volumes from the specified container(s) (default [])[root@VM_0_5_centos ~]# docker run -it centos /bin/bash #启动centos容器并进入交互[root@VM_0_5_centos ~]# docker run -d centos#后台启动centos容器,需要注意:docker容器使用后台运行,就必须要有要一个前台进程,docker发现当前容器没有前台进程使用,就会自动停止
进入容器
docker exec -it 容器id bashshell#进入当前正在运行的容器(开启一个新的终端)docker attach 容器id#进入当前正在运行的容器(直接进入正在运行的终端)
查看运行的容器
docker ps #列出所有运行的容器 docker container listUsage: docker ps [OPTIONS]Options: -a, --all Show all containers (default shows just running) -f, --filter filter Filter output based on conditions provided --format string Pretty-print containers using a Go template --help Print usage -n, --last int Show n last created containers (includes all states) (default -1) -l, --latest Show the latest created container (includes all states) --no-trunc Don't truncate output -q, --quiet Only display numeric IDs -s, --size Display total file sizes[root@VM_0_5_centos ~]# docker ps -a
退出容器
exit #容器停止退出ctrl +P +Q #容器不停止退出
删除容器
docker rm 容器id #删除指定的容器,不能删除正在运行的容器,如果要强制删除 rm -rfdocker rm -f $(docker ps -aq) #删除所有的容器docker ps -a -q|xargs docker rm #删除所有的容器
容器管理
docker start 容器id #启动容器docker restart 容器id #重启容器docker stop 容器id #停止当前正在运行的容器docker kill 容器id #强制停止当前容器
4、其他
[root@VM_0_5_centos ~]# docker logs --helpUsage: docker logs [OPTIONS] CONTAINERFetch the logs of a containerOptions: --details Show extra details provided to logs -f, --follow Follow log output --help Print usage --since string Show logs since timestamp --tail string Number of lines to show from the end of the logs (default "all") -t, --timestamps Show timestamps[root@VM_0_5_centos ~]docker run -d centos /bin/sh -c "while true;do echo hello;sleep 1;done" #模拟日志 -tf#显示日志信息(一直更新) --tail number #需要显示日志条数docker logs -t --tail n 容器id #查看n行日志docker logs -ft 容器id #跟踪日志docker top 容器id#查看容器中的进程信息docker inspect 容器id#查看镜像元数据#将指定容器内资源拷贝到主机docker cp 容器id:容器内路径 主机目的路径[root@VM_0_5_centos html]# docker cp 2d30adc4ee87:/etc/nginx/nginx.conf .#将主机内资源拷贝到指定容器docker cp 主机目的路径容器id:容器内路径[root@VM_0_5_centos html]# docker cp test.html d1cf09d31eda:/usr/localdocker save centos > /opt/centos.tar.gz # 导出docker镜像至本地docker load < /opt/centos.tar.gz #导入本地镜像到docker镜像库docker stats # 查看docker容器使用内存情况
#1. 搜索镜像 search 上hub.docker.com搜索[root@localhost docker]# docker search mysql#2. 拉取镜像 pull[root@localhost docker]# docker pull mysql:5.7#3、运行测试# -d 后台运行# --name 给容器命名# -p 宿主机端口:容器内部端口[root@localhost docker]# docker run -d -p 3355:3306 -e MYSQL_ROOT_PASSWORD=root --name mysql1 mysql:5.7#测试 (关闭防火墙或授权远程访问) 外部连接访问正常 #进入容器命令:docker exec -it 容器id /bin/bash ------------------------------容器启动进行数据挂载-----------------------------#mysql容器正常启动 数据挂载成功 $ docker run -d -p 3344:3306 -v /data/mysql/conf:/etc/mysql/conf.d -v /data/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root --name mysql01 mysql:5.7# 开放3344端口及安全组,在容器内部创建用户并授权,测试OK
2、安装tomcat
#拉取镜像[root@localhost docker]# docker pull tomcat #创建容器运行[root@localhost docker]# docker run -d -p 8080:8080 --name mytomcat tomcat#测试访问(记得关闭防火墙 此时会发现访问成功出现404响应,原因是webapps下面没有资源)ip:8080#进入容器(可以使用容器名称或容器id)[root@localhost docker]# docker exec -it mytomcat /bin/bash#将webapps.dist下的内容复制到webapps下面,重新访问即可看到正常页面root@ebef54554573:/usr/local/tomcat# cp -r webapps.dist/* webapps/#部署项目到tomcat[root@localhost docker]# docker cp demo.war mytomcat:/usr/local/tomcat/webapps/#重启tomcat容器[root@localhost docker]# docker restart mytomcat
3、安装nginx
#直接安装nginx镜像运行nginx容器[root@localhost docker]# docker run -d -p 80:80 --name mynginx nginx#进入容器 复制文件到容器同上,不在累述#数据挂载 (把容器文件挂载到宿主机上面,不用每次进入容器内部修改文件,在容器外部宿主机修改文件会自动同步到容器内部,并且当删除容器后,宿主机上的文件不会被删除,相当于对容器内部文件进行持久化操作。)[root@localhost docker]# docker run -d -p 8033:80 -v /data/nginx/html:/usr/share/nginx/html --name mynginx nginx#在/data/nginx/html目录下创建test.html[root@localhost html]# vim test.html#测试成功[root@localhost html]# curl localhost:8033/test.html--------------------------------------------------------------------------------------[root@localhost docker]# docker run -dit -p 8081:80 -v /data/nginx/conf:/etc/nginx -v /data/nginx/log:/var/log/nginx -v /data/nginx/html:/usr/share/nginx/html --name nginx1 nginx /bin/bash# 说明:当挂载容器内安装软件配置目录容易出现很多问题,比如:1.后台启动即退出 2.成功启动后无法正常访问[root@VM_0_5_centos conf]# curl localhost:8081curl: (56) Recv failure: Connection reset by peer
4、安装elasticsearch
[root@VM_0_5_centos conf]# docker run -d -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms64m -Xmx512m" --name elasticsearch elasticsearch:7.6.2[root@VM_0_5_centos conf]# curl localhost:9200{ "name" : "b7546b5af212", "cluster_name" : "docker-cluster", "cluster_uuid" : "ZLQNTv0ZRbuQAXw4ZLJezw", "version" : { "number" : "7.6.2", "build_flavor" : "default", "build_type" : "docker", "build_hash" : "ef48eb35cf30adf4db14086e8aabd07ef6fb113f", "build_date" : "2020-03-26T06:34:37.794943Z", "build_snapshot" : false, "lucene_version" : "8.4.0", "minimum_wire_compatibility_version" : "6.8.0", "minimum_index_compatibility_version" : "6.0.0-beta1" }, "tagline" : "You Know, for Search"}
五、Docker镜像
操作系统分为内核和用户空间。对于Linux而言,内核启动后,会挂载root文件系统为其提供用户空间支持。而Docker镜像(Image) ,就相当于是一个root文件系统。比如官方镜像centos:7.6就包含了完整的一套centos 7.6最小系统的root文件系统。
Docker镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。
因为镜像包含操作系统完整的root文件系统,其体积往往是庞大的,因此在Docker设计时将其设计为分层存储的架构。镜像只是一个虚拟的概念,其实际体现并非由一个文件组成,而是由一组文件系统组成,或者说,由多层文件系统联合组成。 ? 镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。在构建镜像的时候,需要额外小心,每一层尽量只包含该层需要添加的东西,任何额外的东西应该在该层构建结束前清理掉。 ? 分层存储的特征还使得镜像的复用、定制变的更为容易。甚至可以用之前构建好的镜像作为基础层,然后进一步添加新的层,以定制自己所需的内容,构建新的镜像。
镜像使用的是分层存储,容器也是如此。每一个容器运行时,是以镜像为基础层,在其上创建一个当前容器的存储层,我们可以称这个为容器运行时读写而准备的存储层为容器存储层。容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。
镜像使用分层存储,便于资源共享,可以使用docker image inspect 命令查看
[root@VM_0_5_centos ~]# docker image inspect nginx
#docker commit 提交容器成为一个新的副本docker commit -m="描述信息" -a="作者" 容器id 目标镜像名:[TAG][root@VM_0_5_centos ~]# docker commit -a="shuaige" -m="add test.html" 8fc157a704d4 nginx3:1.0sha256:31b98e5522104b8a30b2cf587cc0494a265f4de9df3a7b04c45f78cb5b9001ef[root@VM_0_5_centos ~]# docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEnginx3 1.0 31b98e552210 14 seconds ago 132 MBdocker.io/nginx latest 2622e6cca7eb 5 days ago 132 MB
六、Docker数据挂载
按照Docker最佳实践的要求,容器不应该向其存储层内写入任何数据,容器存储层要保持无状态化。所有的文件写入操作,都应该使用Volume数据卷、或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。 ? 数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此,使用数据卷后,容器删除或者重新运行之后,数据却不会丢失。
数据卷的特性数据卷可以在容器之间共享和重用对数据卷的修改会立马生效对数据卷的更新,不会影响镜像数据卷默认会一直存在,即使容器被删除数据卷命令#创建一个数据卷docker volume create 数据卷名称#查看所有的数据卷docker volume ls#查看指定数据卷的信息docker volume inspect 数据卷名称#创建一个tomcat容器 使用数据卷挂载容器内目录[root@VM_0_5_centos conf]# docker run --name my-tomcat -d -p 8080:8080 -v test:/wepapps tomcat:latest#删除数据卷 如果需要在删除容器的同时移除数据卷,可以在删除容器的时候使用dockerrm-v这个命令。docker volume rm 数据卷名称#清理无主的数据卷docker volume prune
使用数据卷进行数据挂载
#docker run -it -v 主机目录:容器内目录 -p 主机端口:容器内端口-v, --volume list Bind mount a volume (default []) --volume-driver string Optional volume driver for the container --volumes-from list Mount volumes from the specified container(s) (default [])[root@VM_0_5_centos ~]# docker run -it -v /data/test:/home centos /bin/bash#通过 docker inspect 容器id 查看[root@VM_0_5_centos test]# docker inspect 523d102d8586"Mounts": [ { "Type": "bind", "Source": "/data/test", "Destination": "/home", "Mode": "", "RW": true, "Propagation": "rprivate" } ]
#启动mysql容器进行数据挂载[root@VM_0_5_centos test]# 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#查看mysql容器id[root@VM_0_5_centos test]# docker psCONTAINER ID IMAGE PORTS NAMES0c10d400ecd8 mysql:5.7 0.0.0.0:3306->3306/tcp, 33060/tcp mysql01#进入mysql容器[root@VM_0_5_centos test]# docker exec -it 0c10d400ecd8 /bin/sh# mysql -uroot -pEnter password: #进入mysql创建新用户并授权访问mysql> create user tom identified by "123456";mysql> grant all on *.* to tom;mysql> flush privileges;#开放端口[安全组](或关闭防火墙) 外部测试OK
挂载的几种具体方式
# 匿名挂载-v 容器内路径(只写容器内的路径,没有指定宿主机的路径)[root@VM_0_5_centos conf]# docker run -d -P -v /etc/nginx --name nginx01 nginx# 查看所有的volume的情况[root@VM_0_5_centos conf]# docker volume lsDRIVER VOLUME NAMElocal 8a2947a4d63c5c71db7dd331a487cd2dcfacc65022302f42030f7deea799b203 # 具名挂载-v 卷名:容器内路径[root@VM_0_5_centos conf]# docker run -d -P -v my-nginx:/etc/nginx --name nginx02 nginx[root@VM_0_5_centos conf]# docker volume ls DRIVER VOLUME NAMElocal my-nginx# 查看一下这个卷信息[root@VM_0_5_centos conf]# docker volume inspect my-nginx[ { "Driver": "local", "Labels": null, "Mountpoint": "/var/lib/docker/volumes/my-nginx/_data", "Name": "my-nginx", "Options": {}, "Scope": "local" }]
注意:所有的docker容器内的卷,没有指定目录的情况下都是在/var/lib/docker/volumes/xxxx/_data下,如果指定了目录,docker volume ls 是查看不到的
# ro rw 改变读写权限ro #readonly 只读 说明这个路径只能通过宿主机来操作,容器内部无法操作!rw #readwrite 可读可写docker run -d -P -v nginx03:/etc/nginx:ro --name nginx03 nginxdocker run -d -P -v nginx04:/etc/nginx:rw --name nginx04 nginx
数据卷容器(多个容器同步数据)
案例:多个mysql数据共享(mysql01和mysql02共享/data/mysql/data目录下数据)
---------------------------------目前存在问题,待解决----------------------------------$ docker run -d -p 3344:3306 -v /data/mysql/conf:/etc/mysql/conf.d -v /data/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root --name mysql01 mysql:5.7$ docker run -d -p 3355:3306 --volumes-from mysql01 -e MYSQL_ROOT_PASSWORD=root --name mysql02 mysql:5.7# 挂载配置文件出现报错ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)
容器之间的配置信息的传递,数据卷容器的生命周期一直持续到没有容器使用为止。但是一旦持久化到了本地,这个时候,本地的数据是不会删除的!
七、DockerFile构建私有镜像DockerFile构建镜像步骤镜像的定制实际上就是定制每一层所添加的配置、文件。我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,这个脚本就是Dockerfile。 ? Dockerfile是一个文本文件,其内包含了一条条的指令,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
1、 编写一个DockerFile文件
2、 docker build 构建成为一个镜像
3、 docker run运行镜像
4、 docker push发布镜像(DockerHub 、阿里云仓库)
DockerFile详解FROM# 基础镜像,一切从这里开始构建MAINTAINER# 镜像是谁写的, 姓名+邮箱RUN# 镜像构建的时候需要运行的命令COPY# 类似ADD,将我们文件拷贝到镜像中ADD# 步骤,tomcat镜像,这个tomcat压缩包!添加内容到同目录WORKDIR# 镜像的工作目录VOLUME# 挂载的目录EXPOSE# 暴露端口配置CMD# 指定这个容器启动的时候要运行的命令,只有最后一个会生效,可被替代。ENTRYPOINT# 指定这个容器启动的时候要运行的命令,可以追加命令ONBUILD# 当构建一个被继承 DockerFile 这个时候就会运行ONBUILD的指令,触发指令。ENV# 构建的时候设置环境变量!
注意:每个关键字(指令)都必须是大写字母,自上到下执行,每一个指令都会创建提交一个新的镜像层。
FROM指定基础镜像 ? 所谓定制镜像,一定是以一个镜像为基础,在其上进行定制。基础镜像是必须指定的,而FROM就是指定基础镜像,因此一个Dockerfile中FROM是必备的指令,并且必须是第一条指令。 ? 在Docker Hub上有非常多的高质量的官方镜像,有可以直接拿来使用的服务类的镜像,如nginx、redis、mysql 、tomcat等;可以在其中寻找一个最符合我们最终目标的镜像为基础镜像进行定制。 ? 如果没有找到对应服务的镜像,官方镜像中还提供了一些更为基础的操作系统镜像,如ubuntu、debian、centos、 alpine等, 这些操作系统的软件库为我们提供了更广阔的扩展空间。 除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为scratch。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。 ? FROM scratch ? 如果你以scratch为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。对于Linux下静态编译的程序来说,并不需要有操作系统提供运行时支持,所需的一切库都已经在可执行文件里了,因此直接FROM scratch会让镜像体积更加小巧。使用Go语言开发的应用很多会使用这种方式来制作镜像,这也是为什么有人认为Go是特别适合容器微服务架构的语言的原因之一。
RUN执行命令 ? RUN指令是用来执行命令行命令的。由于命令行的强大能力,RUN 指令在定制镜像时是最常用的指令之一。其格式有两种: .
shell格式: RUN <命令>RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.htmlexec格式: RUN ["可执行文件", "参数1", "参数2"]RUN tar -xzf redis.tar.gz -C /usr/src/redis -strip-components=1RUN make -C /usr/src/redisRUN make -C /usr/src/redis install
使用Dockerfile定制nginx镜像
[root@VM_0_5_centos docker]# vim DockerfileFROM nginxRUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html[root@VM_0_5_centos docker]# docker build -t mynginx:1.0 .[root@VM_0_5_centos docker]# docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEmynginx 1.0 ae280f7cccc1 About a minute ago 132 MB[root@VM_0_5_centos docker]# docker run -d -p 8081:80 --name mynginx1 mynginx:1.0[root@VM_0_5_centos docker]# curl localhost:8081<h1>Hello, Docker!</h1>
COPY 复制文件 ? 格式: ? ●COPY <源路径>... <目标路径> ? ●COPY ["<源路径1>",..."<目标路径>"] ? COPY指令将从构建上下文目录中<源路径>的文件/目录复制到新的一层的镜像内的<目标路径>位置。比如:
COPY package.json /usr/src/app/
<源路径>可以是多个,甚至可以是通配符,如:
COPY hom*/mydir/COPY hom?.txt /mydir/
ADD更高级的复制文件 ? ADD指令和COPY的格式和性质基本一致。但是在COPY基础上增加了一些功能。比如<源路径>可以是一个URL,这种情况下,Docker 引擎会试图去下载这个链接的文件放到<目标路径>去。 ? 在Docker官方的Dockerfile最佳实践文档中要求,尽可能的使用COPY,因为COPY的语义很明确,就是复制文件而已,而ADD则包含了更复杂的功能,其行为也不一定很清晰。最适合使用ADD的场合,就是所提及的需要自动解压缩的场合。因此在COPY和ADD指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用COPY指令,仅在需要自动解压缩的场合使用ADD。
CMD容器启动命令 ? CMD指令的格式和RUN相似,也是两种格式: ? ●shell格式: CMD <命令> ? ●exec格式: CMD ["可执行文件", "参数1", "参数2"..] ? ●参数列表格式: CMD ["参数1", "参数2..]。在指定了ENTRYPOINT指令后,用CMD指定具体的参数。 ? Docker不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。CMD 指令就是用于指定默认的容器主进程启动命令的。
ENTRYPOINT入口点 ? ENTRYPOINT的目的和CMD一样,都是在指定容器启动程序及参数。ENTRYPOINT在运行时也可以替代,不过比CMD要略显繁琐,需要通过docker run的参数--entrypoint来指定。当指定了ENTRYPOINT后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将CMD的内容作为参数传给ENTRYPOINT指令,换句话说实际执行时,将变为:ENTRYPOINT "CMD"
#CMD和ENTRYPOINT的区别CMD# 指定这个容器启动的时候要运行的命令,只有最后一个会生效,可被替代。ENTRYPOINT# 指定这个容器启动的时候要运行的命令,可以追加命令
ENV设置环境变量 ? 格式有两种: ? ●ENV key value ? ●ENV key1=value1 key2=value2... ? 这个指令很简单,就是设置环境变量而已,无论是后面的其它指令,如RUN,还是运行时的应用,都可以直接使用这里定义的环境变量。
ENV VERSION=1.0 DEBUG=on NAME="Happy Feet"
$VERSION ? #使用环境变量 ? 下列指令可以支持环境变量展开: ADD、COPY、ENV、 EXPOSE、 LABEL、USER、 WORKDIR、 VOLUME、 STOPSIGNAL、 ONBUILD 。
ARG 构建参数 ? 格式:ARG <参数名>[=<默认值>] ? 构建参数和ENV的效果一样,都是设置环境变量。所不同的是,ARG 所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。但是不要因此就使用ARG保存密码之类的信息,因为docker history还是可以看到所有值的。Dockerfile中的ARG指令是定义参数名称,以及定义其默认值。该默认值可以在构建命令docker build中用--build-arg <参数名>=<值>来覆盖。
VOLUME 定义匿名卷 ? 格式: ? VOLUME ["<路径1>", "<路径2> ..] ? VOLUME <路径> ? 容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中,为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在Dockerfile中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。 ? VOLUME /data ? 这里的/data目录就会在运行时自动挂载为匿名卷,任何向/data中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行时可以覆盖这个挂载设置。 ? eg:docker run -d -V mydata:/data XXXX ? 在这行命令中,就使用了mydata这个命名卷挂载到了/data这个位置,替代了Dockerfile中定义的匿名卷的挂载配置。
EXPOSE声明端口 ? 格式:EXPOSE <端口1> [<端口2...]。 ? EXPOSE指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。 ? 在Dockerfile中写入这样的声明有两个好处: ? 1.是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射; ? 2.在运行时使随机端口映射时,也就是docker run -P时,会自动随机映射EXPOSE的端口。
WORKDIR 指定工作目录 ? 格式:WORKDIR <工作目录路径>。 ? 使用WORKDIR指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR 会帮你建立目录。之前提到一些初学者常犯的错误是把Dockerfile等同于Shell脚本来书写,这种错误的理解还可能会导致出现下面这样的错误: RUN cd /app RUN echo "hello" > world.txt 如果将这个Dockerfile进行构建镜像运行后,会发现找不到/app/world.txt文件。
原因:在 Shell中,连续两行是同一个进程执行环境,因此前一个命令修改的内存状态,会直接影响后一个命令,而在Dockerfile中,这两行RUN命令的执行环境根本不同,是两个完全不同的容器。 ? 这就是对Dockerfile构建分层存储的概念不了解所导致的错误。 ? 每一个RUN都是启动一个容器、执行命令、然后提交存储层文件变更。 ? 第一层RUN cd /app的执行仅仅是当前进程的工作目录变更,一个内存上的变化而已,其结果不会造成任何文件变更。而到第二层的时候,启动的是一个全新的容器,跟第一层的容器更完全没关系,自然不可能继承前一层构建过程中的内存变化。 ? 因此如果需要改变以后各层的工作目录的位置,那么应该使用WORKDIR指令。
USER 指定当前用户 ? 格式: USER <用户名> ? USER指令和WORKDIR相似,都是改变环境状态并影响以后的层。WORKDIR 是改变工作目录,USER 则是改变之后层的执行RUN , CMD以及ENTRYPOINT这类命令的身份。 ? 当然,和WORKDIR一样,USER 只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。 ? RUN groupadd -r redis & useradd -r -g redis redis ? USER redis ? RUN [ "redis-server" ]
HEALTHCHECK健康检查 ? 格式:HEALTHCHECK [选项] CMD <命令> :设置检查容器健康状况的命令 ? HEALTHCHECK NONE :如果基础镜像有健康检查指令,可以屏蔽掉其健康检查指令 ? HEALTHCHECK指令是告诉Docker应该如何进行判断容器的状态是否正常,这是Docker1.12引入的新指令。通过该指令指定一行命令,用这行命令来判断容器主进程的服务状态是否还正常,从而比较真实的反应容器实际状态。
一个镜像指定了HEALTHCHECK指令后,用其启动容器,初始状态会为starting,在执行健康检查成功后变为healthy,如果连续一定次数失败, 则会变为unhealthy。 ? HEALTHCHECK支持下列选项: ? ●--interval=<间隔> :两次健康检查的间隔, 默认为30秒; ? ●--timeout=<时长> :健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认30秒; ? ●--retries=<次数> :当连续失败指定次数后,则将容器状态视为unhealthy, 默认3次。 ? 为了帮助排障,健康检查命令的输出(包括 stdout以及stderr)都会被存储于健康状态里,可以用docker inspect来查看。
ONBUILD为他人做嫁衣裳 ? 格式: ONBUILD <其它指令>。 ? ONBUILD是一个特殊的指令,它后面跟的是其它指令,比如RUN , COPY等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。 ? Dockerfile中的其它指令都是为了定制当前镜像而准备的,唯有ONBUILD是为了帮助别人定制自己而准备的。
# 1.编写Dockerfile文件 重新构建centos镜像[root@VM_0_5_centos docker]# vim dockerfile-centosFROM centosMAINTAINER shuaige<669104343@qq.com>ENV MYPATH /usr/localWORKDIR $MYPATHRUN yum -y install vimRUN yum -y install net-toolsEXPOSE 80CMD echo $MYPATHCMD echo "-----end----"CMD /bin/bash# 2.执行 docker build 命令进行构建# 命令 docker build -f 文件路径 -t 镜像名:[tag] .[root@VM_0_5_centos docker]# docker build -f mydockerfile-centos -t mycentos:1.0 .# 3.启动容器并进入测试ifconfig、vim[root@VM_0_5_centos docker]# docker run -it mycentos:1.0
制作tomcat镜像
#资源准备 创建/usr/local/docker/tomcat目录,将jdk、tomcat、readme压缩包放到该目录下# 1.编写Dockerfile文件[root@VM_0_5_centos tomcat]# vim DockerfileFROM centos MAINTAINER shuaige<669104343@qq.com>COPY readme /usr/local/readmeADD jdk-8u192-linux-x64.tar.gz /usr/local/ADD apache-tomcat-8.0.53.tar.gz /usr/local/RUN yum -y install vimENV MYPATH /usr/localWORKDIR $MYPATHENV JAVA_HOME /usr/local/jdk1.8.0_192ENV CATALINA_HOME /usr/local/apache-tomcat-8.0.53ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/libEXPOSE 8080CMD /usr/local/apache-tomcat-8.0.53/bin/startup.sh && tail -F /usr/local/apache-tomcat-8.0.53/logs/catalina.out# 2.构建镜像[root@VM_0_5_centos tomcat]# docker build -t mytomcat:1.0 .# 3.运行镜像docker run -d -p 8080:8080 -v /data/tomcat/webapps:/usr/local/apache-tomcat-8.0.53/webapps -v /data/tomcat/logs/:/usr/local/apache-tomcat-8.0.53/logs --name tomcat01 mytomcat:1.0# 4.本地发布项目测试 测试OKhttp://49.234.90.25:8080/test/index.html
其他制作镜像方式
docker save和docker load Docker还提供了docker load和docker save命令,用以将镜像保存为一个tar文件,然后传输到另一个位置.上,再加载进来。这是在没有Docker Registry时的做法,现在已经不推荐,镜像迁移应该直接使用Docker Registry,无论是直接使用Docker Hub还是使用内网私有Registry 都可以。 例如:保存nginx镜像
docker save nginx|gzip > nginx-latest.tar.gz
然后我们将nginx-latest.tar.gz文件复制到了到了另一个机器上,再次加载镜像:
docker load -i nginx-latest.tar.gz
目前Docker官方维护了一个公共仓库Docker Hub,其中已经包括了数量超过15,000的镜像。大部分需求都可以通过在Docker Hub中直接下载镜像来实现。
注册登录 ? 可以在https://hub.docker.com免费注册一个Docker账号。在命令行界执行docker login输入用户名及密码来完成在命令行界面登录DockerHub,通过docker logout退出登录。
拉取镜像 可以通过docker search命令来查找官方仓库中的镜像,并利用docker pull命令来将它下载到本地。
推送镜像 ? 用户也可以在登录后通过docker push命令来将自己的镜像推送到DockerHub。
# 0.查询将要上传的镜像ID[root@VM_0_5_centos ~]# docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEdocker.io/hello-world latest bf756fb1ae65 5 months ago 13.3 kB# 1.给将要上传的镜像打标签,注意仓库名一定要写正确# docker tag 镜像ID 用户名/仓库名[root@VM_0_5_centos ~]# docker tag bf756fb1ae65 shuaige66/hello-world# 2.登录docker仓库[root@VM_0_5_centos ~]# docker login # 3.推送镜像到Docker Hub[root@VM_0_5_centos ~]# docker push shuaige66/hello-world
2、阿里云仓库
# 1. 登录阿里云Docker Registry 用于登录的用户名为阿里云账号全名,密码为开通服务时设置的密码。$ sudo docker login --username=18271267916 registry.cn-hangzhou.aliyuncs.com# 2. 从Registry中拉取镜像$ sudo docker pull registry.cn-hangzhou.aliyuncs.com/shuaige/test:[镜像版本号]# 3. 将镜像推送到Registry 请根据实际镜像信息替换示例中的[ImageId]和[镜像版本号]参数。$ sudo docker login --username=18271267916 registry.cn-hangzhou.aliyuncs.com$ sudo docker tag [ImageId] registry.cn-hangzhou.aliyuncs.com/shuaige/test:[镜像版本号]$ sudo docker push registry.cn-hangzhou.aliyuncs.com/shuaige/test:[镜像版本号]# 示例# 1.使用"docker images"命令找到镜像,将该镜像名称中的域名部分变更为Registry专有网络地址。[root@VM_0_5_centos docker]# docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEmynginx 1.0 ae280f7cccc1 5 hours ago 132 MB# 2.使用"docker tag"命令重命名镜像,并将它通过专有网络地址推送至Registry。docker tag ae280f7cccc1 registry.cn-hangzhou.aliyuncs.com/shuaige/mynginx:1.0# 3.推送到阿里云镜像仓库docker push registry.cn-hangzhou.aliyuncs.com/shuaige/mynginx:1.0
3、私有仓库
有时候使用Docker Hub这样的公共仓库可能不方便,用户可以创建一个本地仓库供私人使用。比如,基于公司内部项目构建的镜像。 ? docker-registry是官方提供的工具,可以用于构建私有的镜像仓库。 ? 安装运行docker-registryv ? 通过获取官方registry镜像来运行。默认情况下,仓库会被创建在容器的/var/lib/registry目录下,可以通过-v参数来将镜像文件存放在本地的指定路径。
[root@VM_0_5_centos ~]# docker run -d -p 5000:5000 --restart=always -v /opt/data/registry:/var/lib/registry --name registry registry
在私有仓库上传、搜索、下载镜像 ? 创建好私有仓库之后,就可以使用docker tag来标记一个镜像, 然后推送它到仓库。
# 先在本机查看已有的镜像。[root@VM_0_5_centos ~]# docker images# 使用docker tag将hello-world:latest这个镜像标记为127.0.0.1:5000/hello-world:latest[root@VM_0_5_centos ~]# docker tag hello-world:latest 127.0.0.1:5000/hello-world:latest# 使用docker push上传标记的镜像[root@VM_0_5_centos ~]# docker push 127.0.0.1:5000/hello-world:latest9c27e219663c: Pushed latest: digest: sha256:90659bf80b44ce6be8234e6ff90a1ac34acbeb826903b02cfa0da11c82cbc042 size: 525# 用curl查看仓库中的镜像[root@VM_0_5_centos docker]# curl 127.0.0.1:5000/v2/_catalog{"repositories":["hello-world"]}# 如果可以看到{" repositoris":["hello-world"]},表明镜像已经被成功上传了。# 先删除已有镜像,再尝试从私有仓库中下载这个镜像。docker image rm 127.0.0.1:5000/hello-world:latestdocker pull 127.0.0.1:5000/hello-world:latest
注意: 如果你不想使用127.0.0.1:5000作为仓库地址,比如想让本网段的其他主机也能把镜像推送到私有仓库。你就得把例如192.168.74.149:5000这样的内网地址作为私有仓库地址,这时你会发现依然无法成功推送镜像。 原因:Docker默认不允许非HTTPS方式推送镜像。我们可以通过Docker的配置选项来取消这个限制。
{"regstry-mirror": ["https://registry.docker-cn.com"], "insecure- registries":["192.168.74.149:5000"]}
参考网址
#docker版本及内核兼容性选择https://blog.csdn.net/qq767852841/article/details/106520589/https://www.jianshu.com/p/348dca0e314chttps://docs.docker.com/engine/release-notes/prior-releases/#docker使用https://www.cnblogs.com/peng104/p/10296717.htmlhttps://www.cnblogs.com/zhangxingeng/p/11236968.htmlhttps://blog.csdn.net/forezp/article/details/82454600https://www.javazhiyin.com/62555.htmlhttps://www.javazhiyin.com/?post_type=post&s=dockerhttps://blog.csdn.net/xiaozecheng/article/details/106145593https://blog.csdn.net/xiaozecheng/article/details/106165491
电脑