Docker
认识Docker
文档地址 https://docs.docker.com/ 仓库地址 https://hub.docker.com/
传统虚拟机技术缺点
资源占用多 冗余步骤多 启动很慢
容器化技术
容器化技术不是模拟一个完整的操作系统 容器运行在宿主机上,没有自己的内核,很轻巧 每个容器是相互隔离的,有自己的文件系统,互不影响
有比虚拟机更少的抽象层,不用抽象硬件,利用宿主机的内核
DevOps(开发运维)
应用更快速的交付和部署 打包镜像发布测试,一键运行 更便捷的升级和扩缩容 部署应用就如同小朋友搭积木一样 开发测试环境高度一致
镜像(image)
通过镜像可以创建出多个容器
容器(container)
启动,停止,删除等基本命令进行操作,可以理解为一个简易的Linux
仓库(repository)
存放镜像的地方。公有仓库docker hub(默认是国外的,阿里云配置镜像加速)
安装Docker
查看云主机
系统内核
[root@10-9-48-229 ~]# uname -r 3.10.0-957.1.3.el7.x86_64
系统版本
[root@10-9-48-229 ~]# cat /etc/os-release NAME="CentOS Linux" VERSION="7 (Core)" ID="centos" ID_LIKE="rhel fedora" VERSION_ID="7" PRETTY_NAME="CentOS Linux 7 (Core)" ANSI_COLOR="0;31" CPE_NAME="cpe:/o:centos:centos:7" HOME_URL="https://www.centos.org/" BUG_REPORT_URL="https://bugs.centos.org/" CENTOS_MANTISBT_PROJECT="CentOS-7" CENTOS_MANTISBT_PROJECT_VERSION="7" REDHAT_SUPPORT_PRODUCT="centos" REDHAT_SUPPORT_PRODUCT_VERSION="7"
安装教程
https://docs.docker.com/engine/install/centos/
安装 gcc 环境
yum -y install gcc
yum -y install gcc-c++
关闭docker
systemctl stop docker
卸载旧的版本
sudo yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-engine
卸载docker
sudo yum remove docker-ce docker-ce-cli containerd.io
移除目录
sudo rm -rf /var/lib/docker
需要的安装包
sudo yum install -y yum-utils
设置镜像的仓库
sudo yum-config-manager \ --add-repo \ http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
更新yum软件包索引
yum makecache fast
安装Docker引擎(ce表示社区版,ee表示企业版)
sudo yum install docker-ce docker-ce-cli containerd.io
启动Docker
systemctl start docker
docker version
开机启动
# CentOS 7 新式语法 systemctl start docker.service systemctl enable docker.service
hello world
docker run hello-world
查看该hello-world镜像
镜像加速(尽管我买的是Ucloud云主机,还是可以去注册阿里云账号,生成如下信息!阿里云NB丶)
sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": ["https://xnoai74w.mirror.aliyuncs.com"] } EOF sudo systemctl daemon-reload sudo systemctl restart docker
client-server形式
client在Linux上
server是守护进程的形式,可存放各种容器
若 docker-compose version 出现命令不存在,点击查看安装步骤
Docker的命令
帮助命令
https://docs.docker.com/engine/reference/commandline/docker/
docker version #docker版本信息 docker info #docker系统信息,镜像和容器的数量 docker 命令 --help #万能命令
镜像命令
https://docs.docker.com/engine/reference/commandline/images/
查看本地主机上的所有镜像
[root@10-9-48-229 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest bf756fb1ae65 8 months ago 13.3kB
说明
REPOSITORY 镜像的仓库源 TAG 版本标签信息 IMAGE ID 镜像的id CREATED 创建时间 SIZE 镜像的大小
命令可选项
--all , -a 列出所有镜像 --quiet , -q 只显示镜像的id
搜索镜像
[root@10-9-48-229 ~]# docker search mysql
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
mysql MySQL is a widely used, open-source relation… 9918 [OK]
mariadb MariaDB is a community-developed fork of MyS… 3630 [OK]
mysql/mysql-server Optimized MySQL Server Docker images. Create… 723 [OK]
可选项
# 通过收藏来过滤
--filter=STARS=3000
[root@10-9-48-229 ~]# docker search mysql --filter=STARS=3000
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
mysql MySQL is a widely used, open-source relation… 9918 [OK]
mariadb MariaDB is a community-developed fork of MyS… 3630 [OK]
拉取/下载镜像
[root@10-9-48-229 ~]# docker pull mysql
Using default tag: latest #默认是最新的,企业里最好指定版本
latest: Pulling from library/mysql
bf5952930446: Downloading [============================> ] 15.61MB/27.09MB
8254623a9871: Download complete #分层下载,docker image的核心 联合文件系统
938e3e06dac4: Download complete
ea28ebf28884: Download complete
f3cef38785c2: Download complete
894f9792565a: Downloading [===================> ] 5.299MB/13.45MB
Digest: sha256:c358e72e100ab493a0304bda35e6f239db2ec8c9bb836d8a427ac34307d074ed #签名
Status: Downloaded newer image for mysql:latest
docker.io/library/mysql:latest #真实地址 可以这么玩:docker pull 真实地址
指定版本下载
docker pull mysql:5.7 # 版本 要能在 https://hub.docker.com/ 中搜出来
docker images
删除镜像
docker images
docker rmi -f bf756fb1ae65 #一般通过 image id 来删除
docker rmi -f 镜像id 镜像id 镜像id #删除多个镜像
docker rmi -f $(docker images -aq) #删除所有镜像
容器命令
(有了镜像才可以创建容器,现在下载一个centos镜像来进行测试)
下载centos镜像
docker pull centos
新建容器并启动
docker run [可选参数] image 可选参数内容: --name="Name" 容器名称,用来区分容器 -d 后台方式运行 -it 使用交互方式运行,进入容器查看内容 -p 指定容器的端口 -p 8080:8080 -p IP:主机端口:容器端口 -p 主机端口:容器端口(常用) -p 容器端口 容器端口 -P 随机指定端口
启动并进入容器(这个容器,就是一个centos)
[root@10-9-48-229 ~]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE centos latest 0d120b6ccaa8 3 weeks ago 215MB [root@10-9-48-229 ~]# docker run -it centos /bin/bash [root@06312d33166f /]# ls bin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var [root@06312d33166f /]# exit exit [root@10-9-48-229 ~]#
查看当前正在运行的容器
[root@10-9-48-229 /]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
docker ps -a #查看所有运行过的容器
-n=3 #查看最近3个
-q #只显示容器的编号
退出容器
exit #直接退出交互窗口并停止容器
ctrl+p+q #退出交互窗口容器却不停止
删除容器
docker ps -aq
docker rm 容器id #删除指定的容器,不能删除正在运行的,强制删除加-f docker rm -f $(docker ps -aq) #强制删除所有的容器 docker ps -a -q|xargs docker rm #强制删除所有的容器
docker kill 容器id #强制停止当前容器
启动/停止一个运行过的容器
docker start 9786d107a853 docker stop 9786d107a853
常用其他命令
后台启动容器
docker run -d centos #后台启动,此时docker ps发现centos停止了,是因为这个centos没有对外提供服务,没有一个前台进程
查看日志命令
docker logs -tf --tail 10 4cf528d77128
docker run -d centos /bin/sh -c "while true;do echo hahaha;sleep 1;done" #shell脚本让容器打印日志
查看容器内的进程信息
[root@10-9-48-229 /]# docker top 4cf528d77128
UID PID PPID C STIME TTY TIME CMD
root 15545 15529 0 23:21 ? 00:00:00 /bin/sh -c while true;do echo hahaha;sleep 1;done
root 15595 15545 0 23:22 ? 00:00:00 /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/bin/sleep 1
查看镜像的元数据
docker inspect 4cf528d77128
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
[root@10-9-48-229 /]# docker inspect 4cf528d77128 [ { "Id": "4cf528d77128c6c1d97bd4bd0f96806e06b72f86a77816886cdb65b738ec0a52", "Created": "2020-09-03T15:16:52.810046849Z", "Path": "/bin/sh", "Args": [ "-c", "while true;do echo hahaha;sleep 1;done" ], "State": { "Status": "running", "Running": true, "Paused": false, "Restarting": false, "OOMKilled": false, "Dead": false, "Pid": 15545, "ExitCode": 0, "Error": "", "StartedAt": "2020-09-03T15:21:51.301974968Z", "FinishedAt": "2020-09-03T15:17:43.866467683Z" }, "Image": "sha256:0d120b6ccaa8c5e149176798b3501d4dd1885f961922497cd0abef155c869566", "ResolvConfPath": "/var/lib/docker/containers/4cf528d77128c6c1d97bd4bd0f96806e06b72f86a77816886cdb65b738ec0a52/resolv.conf", "HostnamePath": "/var/lib/docker/containers/4cf528d77128c6c1d97bd4bd0f96806e06b72f86a77816886cdb65b738ec0a52/hostname", "HostsPath": "/var/lib/docker/containers/4cf528d77128c6c1d97bd4bd0f96806e06b72f86a77816886cdb65b738ec0a52/hosts", "LogPath": "/var/lib/docker/containers/4cf528d77128c6c1d97bd4bd0f96806e06b72f86a77816886cdb65b738ec0a52/4cf528d77128c6c1d97bd4bd0f96806e06b72f86a77816886cdb65b738ec0a52-json.log", "Name": "/nice_dhawan", "RestartCount": 0, "Driver": "devicemapper", "Platform": "linux", "MountLabel": "", "ProcessLabel": "", "AppArmorProfile": "", "ExecIDs": null, "HostConfig": { "Binds": null, "ContainerIDFile": "", "LogConfig": { "Type": "json-file", "Config": {} }, "NetworkMode": "default", "PortBindings": {}, "RestartPolicy": { "Name": "no", "MaximumRetryCount": 0 }, "AutoRemove": false, "VolumeDriver": "", "VolumesFrom": null, "CapAdd": null, "CapDrop": null, "Capabilities": null, "Dns": [], "DnsOptions": [], "DnsSearch": [], "ExtraHosts": null, "GroupAdd": null, "IpcMode": "private", "Cgroup": "", "Links": null, "OomScoreAdj": 0, "PidMode": "", "Privileged": false, "PublishAllPorts": false, "ReadonlyRootfs": false, "SecurityOpt": null, "UTSMode": "", "UsernsMode": "", "ShmSize": 67108864, "Runtime": "runc", "ConsoleSize": [ 0, 0 ], "Isolation": "", "CpuShares": 0, "Memory": 0, "NanoCpus": 0, "CgroupParent": "", "BlkioWeight": 0, "BlkioWeightDevice": [], "BlkioDeviceReadBps": null, "BlkioDeviceWriteBps": null, "BlkioDeviceReadIOps": null, "BlkioDeviceWriteIOps": null, "CpuPeriod": 0, "CpuQuota": 0, "CpuRealtimePeriod": 0, "CpuRealtimeRuntime": 0, "CpusetCpus": "", "CpusetMems": "", "Devices": [], "DeviceCgroupRules": null, "DeviceRequests": null, "KernelMemory": 0, "KernelMemoryTCP": 0, "MemoryReservation": 0, "MemorySwap": 0, "MemorySwappiness": null, "OomKillDisable": false, "PidsLimit": null, "Ulimits": null, "CpuCount": 0, "CpuPercent": 0, "IOMaximumIOps": 0, "IOMaximumBandwidth": 0, "MaskedPaths": [ "/proc/asound", "/proc/acpi", "/proc/kcore", "/proc/keys", "/proc/latency_stats", "/proc/timer_list", "/proc/timer_stats", "/proc/sched_debug", "/proc/scsi", "/sys/firmware" ], "ReadonlyPaths": [ "/proc/bus", "/proc/fs", "/proc/irq", "/proc/sys", "/proc/sysrq-trigger" ] }, "GraphDriver": { "Data": { "DeviceId": "39", "DeviceName": "docker-253:1-3981408-0121446216aa675898726b0b45453e637f27c99aadef67a80dac0ad2a7377027", "DeviceSize": "10737418240" }, "Name": "devicemapper" }, "Mounts": [], "Config": { "Hostname": "4cf528d77128", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], "Cmd": [ "/bin/sh", "-c", "while true;do echo hahaha;sleep 1;done" ], "Image": "centos", "Volumes": null, "WorkingDir": "", "Entrypoint": null, "OnBuild": null, "Labels": { "org.label-schema.build-date": "20200809", "org.label-schema.license": "GPLv2", "org.label-schema.name": "CentOS Base Image", "org.label-schema.schema-version": "1.0", "org.label-schema.vendor": "CentOS" } }, "NetworkSettings": { "Bridge": "", "SandboxID": "0ae87e99f87b4871af6045e268ca018e47e8e2e74499374540347b62134412ca", "HairpinMode": false, "LinkLocalIPv6Address": "", "LinkLocalIPv6PrefixLen": 0, "Ports": {}, "SandboxKey": "/var/run/docker/netns/0ae87e99f87b", "SecondaryIPAddresses": null, "SecondaryIPv6Addresses": null, "EndpointID": "355add0949820be91fd5c7883ddda6e5de56a5888ee39cc40613acd97b7e2ab4", "Gateway": "172.17.0.1", "GlobalIPv6Address": "", "GlobalIPv6PrefixLen": 0, "IPAddress": "172.17.0.2", "IPPrefixLen": 16, "IPv6Gateway": "", "MacAddress": "02:42:ac:11:00:02", "Networks": { "bridge": { "IPAMConfig": null, "Links": null, "Aliases": null, "NetworkID": "a1ab3ade09e14a0f3b2d9a50da9c4008cbc380f8703d560703a94827b4d8d4f9", "EndpointID": "355add0949820be91fd5c7883ddda6e5de56a5888ee39cc40613acd97b7e2ab4", "Gateway": "172.17.0.1", "IPAddress": "172.17.0.2", "IPPrefixLen": 16, "IPv6Gateway": "", "GlobalIPv6Address": "", "GlobalIPv6PrefixLen": 0, "MacAddress": "02:42:ac:11:00:02", "DriverOpts": null } } } } ]
进入当前正在运行的容器
#通常容器都是使用后台方式运行的,需要进入容器,修改一些配置 #方式一 docker exec -it 容器id bashShell #进入容器后开启一个新的终端,可以在里边操作 [root@10-9-48-229 /]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4cf528d77128 centos "/bin/sh -c 'while t…" 20 minutes ago Up 15 minutes nice_dhawan [root@10-9-48-229 /]# docker exec -it 4cf528d77128 /bin/bash [root@4cf528d77128 /]# #方式二 docker attach 容器id #进入容器正在执行的终端,不会启动新的进程
从容器内拷贝文件到主机上(后期可以用-v卷的方式)
docker cp 容器id:容器内路径 目的主机路径
[root@10-9-48-229 ~]# docker run -it centos /bin/bash
[root@6b790ffe6a2a /]# cd home
[root@6b790ffe6a2a home]# ls
[root@6b790ffe6a2a home]# touch xixixi.java
[root@6b790ffe6a2a home]# exit
exit
[root@10-9-48-229 ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6b790ffe6a2a centos "/bin/bash" 3 minutes ago Exited (0) 7 seconds ago relaxed_perlman
[root@10-9-48-229 ~]# docker cp 6b790ffe6a2a:/home/xixixi.java /home
[root@10-9-48-229 ~]# cd /home
[root@10-9-48-229 home]# ls
dfs mysql tomcat xixixi.java
练习:用docker装Nginx
暴露端口
docker search nginx #搜索镜像 尽量去hub.docker.com上搜索 docker pull nginx #下载镜像 docker images #查看镜像 docker run -d --name nginx01 -p 3344:80 nginx #需开启3344端口 docker ps #查看进程 curl localhost:3344 #本机测试 -bash: curl: command not found 解决 yum update -y && yum install curl -y http://xx.xx.xx.xx:3344/ #浏览器公网访问测试 docker exec -it nginx01 /bin/bash #进入该容器 whereis nginx #找到nginx配置文件 exit #退出 docker ps #查看进程 docker stop #停止
练习:用docker装tomcat
进入容器查看
docker run -it --rm tomcat:9.0 #官方套路,用完即删,关闭就删了,方便测试,docker ps -a是看不到痕迹的 docker pull tomcat #非用完即删方式 docker run -d -p 3355:8080 --name tomcat01 tomcat #启动tomcat docker exec -it tomcat01 /bin/bash #会直接进入root@7a16ef3a9238:/usr/local/tomcat# ls -al (webapps里边是空的,可以从webapps.dist里搞进去) cp webapps.dist/* webapps -r http://xx.xx.xx.xx:3355/
练习:部署ES+kibana
看容器的当前运行状态,进行修改
# es 暴露的端口很多
# es 十分的耗内存
# es 的数据一般需要放置到安全目录丶挂载
# 下载并启动 elasticsearch
docker run -d --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch:7.6.2
docker stats #查看 cpu 状态
curl localhost:9200 # 测试 es 是否成功
# 关闭,启动时限制内存 -e表示环境 但是没有成功启动,Docker容器后台运行,就必须有一个前台进程.
docker run -dit --name elasticsearch02 -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms64m Xmx512m" elasticsearch:7.6.2
docker ps -a
docker rm 92ef80884600
# 使用kibana连接es 未完待续…
可视化工具
portainer # (先用这个) # rancher(CI/CD再用)
docker run -d -p 8088:9000 \ --restart=always -v /var/run/docker.sock:/var/run/docker.sock --privileged=true portainer/portainer # 测试 http://106.75.32.166:8088/
Commit镜像
docker commit -m="提交的描述信息" -a="作者" 容器id 目标镜像:[tag] docker run -it -p 8080:8080 tomcat docker ps #另一个会话窗口,进入tomcat命令行 docker exec -it c32c4530f5e6 /bin/bash cp webapps.dist/* webapps -r #现在要做的就是把当前这个tomcat搞成一个镜像进行提交 exit docker ps docker commit -a="chhh" -m="add webapps app" c32c4530f5e6 tomcat02:1.0 #将这个容器生成一个新的镜像 类似快照 docker images #此时发现镜像已经生成成功了
容器数据卷
数据不放在容器中,容器的持久化和同步操作,容器间共享数据。
方式一:直接用命令来挂载:-v
docker run -it -v /home/ceshi:/home centos /bin/bash docker inspect 4f88d6c7d1cf #查看卷挂载信息 “Mounts” 这个inspect后面还可以跟具名 ,是 VOLUME NAME 都行 #现在分别在容器内和容器外这两个挂载的目录进行新建文件,到对方目录去查看 touch test1.java #停掉容器,去宿主机上修改 test1.java ,完了开启容器查看 (以后改主机上的文件就可以直接修改到容器的文件) docker start 4f88d6c7d1cf docker attach 4f88d6c7d1cf
练习:安装Mysql
docker pull mysql:5.7 #官方测试:docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag #开启端口3310,容器名mysql01 docker run -d -p 3310:3306 -v /home/mysql_docker_test/conf:/etc/mysql/conf.d -v /home/mysql_docker_test/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root --name mysql01 mysql:5.7 #竟然启动成功,没有瞬间关掉丶用mysql客户端连接测试成功! #强行干掉容器,看看挂载到本地的文件还在不在 docker rm -f mysql01
匿名挂载、具名挂载 和 指定路径挂载
# 指定路径挂载 就是指定上面的方式,用绝对路径 # 匿名挂载(通常不用这个),-v 直接跟需要挂载出来的容器内的目录,而不指定外部本地的目录,-P表示随机端口 docker run -d -P --name nginx02 -v /etc/nginx nginx # 具名挂载 docker run -d -P --name nginx03 -v juming-nginx:/etc/nginx nginx # 查看所有 volume 的情况 docker volume ls docker inspect juming-nginx #可查看挂载情况 /var/lib/docker/volumes/juming-nginx/_data 没指定目录都挂到这里 # 另外,通常我们用到ro/rw改变读写权限readonly(只能通过外部宿主机来改写文件)/readwrite(默认) docker run -d -P --name nginx03 -v juming-nginx:/etc/nginx:ro nginx docker run -d -P --name nginx03 -v juming-nginx:/etc/nginx:rw nginx
方式二:DockerFile构建时指定挂载
DockerFile就是用来构建docker镜像的构建文件丶命令脚本
(commit可以生成自己的镜像,同样的,dockerfile也可以生成自己的镜像,并且DockerFile里边进行卷挂载才是最常用的方式,因为以后镜像都是自己构建)
(VOLUME ["volume01","volume02"])相当于就是在容器内建文件名为volume01/volume02的共享文件夹(实际是拷贝的机制)
cd /home mkdir docker-test-volume cd docker-test-volume vim dockerfile1 #通过这个脚本生成镜像,镜像是一层一层的,脚本是一个个命令,每个命令都是一层 FROM centos VOLUME ["volume01","volume02"] CMD echo "-----end------" CMD /bin/bash docker build -f /home/docker-test-volume/dockerfile1 -t chhh/centos . docker images # 发现新的镜像已经有了 docker run -it 46a115c3e58d /bin/bash # 启动 ls -al # 发现 volume01,volume02 这两个目录 cd volume01 touch container.txt # 在外面来查看当前容器的信息 docker inspect fea68c0b5eb5 # 查看卷挂载的路径 cd /var/lib/docker/volumes/aa4b0b6c4c28913b88f5c2cc573d0c58dd8b751d3b7d71c18928c3a7c61934f5/_data # ls 发现挂载成功
容器与容器之间,也可以共享数据
docker run -it --name docker01 chhh/centos ctrl+p+q docker run -it --name docker02 --volumes-from docker01 chhh/centos # 01中加了文件,02对应的目录中就有了此文件 docker run -it --name docker03 --volumes-from docker01 chhh/centos # --volumes-from 的使用是基于docker01镜像已经指定了挂载的目录 # 新建文件,发现可以共享 # 删除容器 docker01 ,其挂载的目录不受影响
容器之间配置信息的传递,数据卷容器的生命周期一直持续到没有容器使用为止; 如果将数据持久化到本地,数据将不再消失。
多个mysql实现数据共享(只需要处理一下挂载的目录)
docker run -d -p 3310:3306 -v /home/mysql_docker_test/conf:/etc/mysql/conf.d -v /home/mysql_docker_test/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root --name mysql01 mysql:5.7 docker run -d -p 3311:3306 -v /home/mysql_docker_test/conf:/etc/mysql/conf.d -v /home/mysql_docker_test/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root --name mysql02 mysql:5.7
DockerFile
以前项目完成了,交付的成品是jar、war等,现在是交付docker镜像
所谓dockerfile,就是用来制作这个交付镜像的核心文件,最终镜像的内容包括环境和源码等等
自制镜像第一步:编写dockerfile
docker run -it centos # 原始centos,vim/ifconfig都是用不了的,现在自制镜像,扩展它的功能 # 新开窗口 cd /home mkdir dockerfile #准备自制镜像 cd dockerfile/ vim mydockerfile-centos
dockerfile具体内容
FROM centos MAINTAINER chhh<810808038@qq.com> ENV MYPATH /usr/local WORKDIR $MYPATH RUN yum -y install vim RUN yum -y install net-tools EXPOSE 80 CMD echo $MYPATH CMD echo "---end---" CMD /bin/bash
根据dockerfile构建镜像
docker build -f mydockerfile-centos -t mycentos:0.1 .
docker images
docker history 466c71bf6845 # 查看镜像的构建过程 庖丁解牛 目无全牛
docker run -it mycentos:0.1
CMD 和 ENTRYPOINT 区别(了解)
CMD # 指定这个容器启动时要运行的命令,只有最后一个会生效,可被替代 ENTRYPOINT # 指定这个容器启动时要运行的命令,可以追加命令
CMD
vim dockerfile-cmd-test FROM centos CMD ["ls","-a"] docker build -f dockerfile-cmd-test -t cmdtest . docker images docker run 57f90ddc37cc # 发现 ls -a 命令生效 docker run 57f90ddc37cc -l # 报错,CMD不能追加 docker run 57f90ddc37cc ls -al # 得行
ENTRYPOINT
vim dockerfile-cmd-entrypoint FROM centos ENTRYPOINT ["ls","-a"] docker build -f dockerfile-cmd-entrypoint -t entrypoint-test . docker run 3bf726e9ed3f docker run 3bf726e9ed3f -l # 不报错
docker中很多命令都很相似,需要对比了解区别
练习:做一个tomcat镜像
# 新建目录 [root@10-9-48-229 tomcat]# pwd /home/chhh/build/tomcat # 把资源搞进来 [root@10-9-48-229 tomcat]# ls apache-tomcat-9.0.37.tar.gz jdk-8u261-linux-x64.tar.gz # 记录 touch readme.txt # 编写 Dockerfile (文件名一字不差) vim Dockerfile #构建镜像 docker build -t diytomcat .
Dockerfile内容如下(# 每一句就是一层)
FROM centos MAINTAINER chhh<810808038@qq.com> COPY readme.txt /usr/local/readme.txt ADD jdk-8u261-linux-x64.tar.gz /usr/local/ ADD apache-tomcat-9.0.37.tar.gz /usr/local/ RUN yum -y install vim ENV MYPATH /usr/local/ WORKDIR $MYPATH ENV JAVA_HOME /usr/local/jdk1.8.0_261 ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.37 ENV CATALINA_BASE /usr/local/apache-tomcat-9.0.37 ENV PATH $PATH:$JAVA_HOME/BIN:$CATALINA_HOME/lib:$CATALINA_HOME/bin EXPOSE 8080 CMD /usr/local/apache-tomcat-9.0.37/bin/startup.sh && tail -F /usr/local/apache-tomcat-9.0.37/bin/logs/catalina.out
启动镜像
docker images # 发现 diytomcat docker run -d -p 9090:8080 --name chhhtomcat -v /home/chhh/build/tomcat/test:/usr/local/apache-tomcat-9.0.37/webapps/test -v /home/chhh/build/tomcat/tomcatlogs/:/usr/local/apache-tomcat-9.0.37/logs diytomcat curl localhost:9090 docker exec -it dd379f79f0b7 /bin/bash
发布项目(热部署)
# /home/chhh/build/tomcat/test 在此挂载的路径下放入项目即可 vim index.jsp <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>以后用docker打包发布</title> </head> <body> Hello World!<br/> <% System.out.println("---my test web logs---"); %> </body> </html> mkdir WEB-INF vim 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>
访问成功,日志也可以在挂载的路径下查看了
http://106.75.32.166:9090/test/
发布自己的镜像
https://hub.docker.com/ docker login -u curryneymar # 密码是大写 # 为自己的镜像添加标签 docker tag 544deda75f5c curryneymar/tomcat:1.0 docker images # 推送到远程 因为服务器连不了外网,所以会失败 docker push curryneymar/tomcat:1.0
发布镜像到阿里云容器服务
容器服务 创建命名空间 创建镜像仓库(选本地仓库)
阿里云NB,没交钱也提供服务!
docker logout sudo docker login --username=810808038@qq.com registry.cn-hangzhou.aliyuncs.com sudo docker tag 544deda75f5c registry.cn-hangzhou.aliyuncs.com/possible2dream/possible2dream-test:1.0 sudo docker push registry.cn-hangzhou.aliyuncs.com/possible2dream/possible2dream-test:1.0
但是还是老中断……
另外,docker save/load 可保存镜像给小伙伴/加载小伙伴给的镜像
Docker网络
Centos网络
[root@10-9-48-229 ~]# 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 2: eth0 内网地址: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1454 qdisc pfifo_fast state UP group default qlen 1000 link/ether 52:54:00:aa:21:ec brd ff:ff:ff:ff:ff:ff inet 10.9.48.229/16 brd 10.9.255.255 scope global eth0 valid_lft forever preferred_lft forever 3: docker0 docker地址: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default link/ether 02:42:de:f6:71:44 brd ff:ff:ff:ff:ff:ff inet 172.17.0.1(这个东西想象成路由器)/16 brd 172.17.255.255 scope global docker0 valid_lft forever preferred_lft forever
启动一个tomcat容器
docker run -d -P --name tomcat01 tomcat
查看容器内部网卡信息
[root@10-9-48-229 ~]# 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 86: eth0@if87: <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 容器ip(发现可以ping通)
[root@10-9-48-229 ~]# 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.065 ms 64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.059 ms
安装了docker,就会有一个网卡docker0桥接模式,使用的技术是evth-pair; 每启动一个docker容器,docker就会为其新分配一个ip。
启动容器后再次执行 ip addr
[root@10-9-48-229 ~]# 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 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1454 qdisc pfifo_fast state UP group default qlen 1000 link/ether 52:54:00:aa:21:ec brd ff:ff:ff:ff:ff:ff inet 10.9.48.229/16 brd 10.9.255.255 scope global eth0 valid_lft forever preferred_lft forever 3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:de:f6:71:44 brd ff:ff:ff:ff:ff:ff inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0 valid_lft forever preferred_lft forever 87: vethf192272@if86: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default link/ether 52:0e:44:3c:2e:52 brd ff:ff:ff:ff:ff:ff link-netnsid 0
再启动一个容器
docker run -d -P --name tomcat02 tomcat
再次查看外部Linux网络(发现网卡又多了一对)
[root@10-9-48-229 ~]# 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 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1454 qdisc pfifo_fast state UP group default qlen 1000 link/ether 52:54:00:aa:21:ec brd ff:ff:ff:ff:ff:ff inet 10.9.48.229/16 brd 10.9.255.255 scope global eth0 valid_lft forever preferred_lft forever 3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:de:f6:71:44 brd ff:ff:ff:ff:ff:ff inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0 valid_lft forever preferred_lft forever 87: vethf192272@if86: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default link/ether 52:0e:44:3c:2e:52 brd ff:ff:ff:ff:ff:ff link-netnsid 0 89: vethf48e178@if88: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default link/ether ca:65:15:58:68:d2 brd ff:ff:ff:ff:ff:ff link-netnsid 1
再进入容器查看网络
[root@10-9-48-229 ~]# docker exec -it tomcat02 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 88: eth0@if89: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0 valid_lft forever preferred_lft forever
网卡成对出现,使用 evth-pair 技术,即一对的虚拟设备接口,docker0充当一个桥梁
docker :172.17.0.1 tomcat01:172.17.0.2 tomcat01:172.17.0.3
用 tomcat02 来 ping tomcat01 (发现可以 ping 通)
[root@10-9-48-229 ~]# 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.078 ms 64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.059 ms
关闭容器,网卡自动消失
--link
容器3 与 容器2 不用通过网络,直接通过服务名ping通
docker run -d -P --name tomcat03 --link tomcat02 tomcat
docker exec -it tomcat03 ping tomcat02 # 反向却ping不通
原理(尽管--link已经不推荐使用了,通常是玩丶自定义网络)
[root@10-9-48-229 ~]# 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 705da20dfb69 172.17.0.4 e4410508e461
自定义网络
目的:容器互连
docker network --help docker network ls # 查看所有docker网络 docker network rm mynet # 移除网络
网络模式
bridge:桥接(自定义网络也用这个) none:不配置网络 host:和宿主机共享网络 container:容器网络连通(用的少,局限性大)
自定义网络 并 使用
# 启动容器,默认带有一个 --net bridge ,就是开启docker0,这个东西可以自定义
docker run -d -P --name tomcat01 --net bridge tomcat
docker0 域名不能访问,需要自定义一个网络:
docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet
# 题外话:192.168.0.0/16,看到这种东西,表示前16位是固定的,即192.168是固定的,后面的自由度为255*255
docker network ls
docker network inspect mynet
docker run -d -P --name tomcat-net-01 --net mynet tomcat
docker run -d -P --name tomcat-net-02 --net mynet tomcat
docker network inspect mynet # 精彩
docker exec -it tomcat-net-01 ping 192.168.0.3
docker exec -it tomcat-net-01 ping tomcat-net-02 # Temporary failure in name resolution 重启,删了重试,解决
(不同的集群使用不同的网络,保证集群健康丶使网络不连通)
网络连通
不同的网络上的容器之间实现互ping(docker0和mynet)
docker run -d -P --name tomcat-net-01 --net mynet tomcat docker run -d -P --name tomcat-net-02 --net mynet tomcat docker run -d -P --name tomcat01 tomcat docker run -d -P --name tomcat02 tomcat docker ps # 尝试从 docker0 上的 tomcat01 直接 ping mynet 上的 tomcat-net-01 显然会撞墙 docker exec -it tomcat01 ping tomcat-net-01 # docker network connect --help 学会查文档 # 将容器 tomcat01 同网络 mynet 连起来 docker network connect mynet tomcat01 docker network inspect mynet # 这会再用容器 tomcat01 ping mynet 网络就可以通了
Redis集群部署实战
(分片+高可用+负载均衡)
建redis的网卡驱动、子网
docker network create --driver bridge --subnet 172.38.0.0/16 --gateway 172.38.0.1 redis
docker network ls
docker network inspect redis
通过脚本创建6个redis配置(每个文件夹写入对应配置)(这个脚本搞错了会发现后面启动不了)
for port in $(seq 1 6); do mkdir -p /mydata/redis/node-${port}/conf touch /mydata/redis/node-${port}/conf/redis.conf cat << EOF >/mydata/redis/node-${port}/conf/redis.conf port 6379 bind 0.0.0.0 cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 cluster-announce-ip 172.38.0.1${port} cluster-announce-port 6379 cluster-announce-bus-port 16379 appendonly yes EOF done
启动redis节点
# 节点1 docker run -p 6371:6379 -p 16371:16379 --name redis-1 \ -v /mydata/redis/node-1/data:/data \ -v /mydata/redis/node-1/conf/redis.conf:/etc/redis/redis.conf \ -d --net redis --ip 172.38.0.11 redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf # 节点2 docker run -p 6372:6379 -p 16372:16379 --name redis-2 \ -v /mydata/redis/node-2/data:/data \ -v /mydata/redis/node-2/conf/redis.conf:/etc/redis/redis.conf \ -d --net redis --ip 172.38.0.12 redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf # 节点3 docker run -p 6373:6379 -p 16373:16379 --name redis-3 \ -v /mydata/redis/node-3/data:/data \ -v /mydata/redis/node-3/conf/redis.conf:/etc/redis/redis.conf \ -d --net redis --ip 172.38.0.13 redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf # 节点4 docker run -p 6374:6379 -p 16374:16379 --name redis-4 \ -v /mydata/redis/node-4/data:/data \ -v /mydata/redis/node-4/conf/redis.conf:/etc/redis/redis.conf \ -d --net redis --ip 172.38.0.14 redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf # 节点5 docker run -p 6375:6379 -p 16375:16379 --name redis-5 \ -v /mydata/redis/node-5/data:/data \ -v /mydata/redis/node-5/conf/redis.conf:/etc/redis/redis.conf \ -d --net redis --ip 172.38.0.15 redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf # 节点6 docker run -p 6376:6379 -p 16376:16379 --name redis-6 \ -v /mydata/redis/node-6/data:/data \ -v /mydata/redis/node-6/conf/redis.conf:/etc/redis/redis.conf \ -d --net redis --ip 172.38.0.16 redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf
以交互模式进入redis节点内,创建redis集群
[root@10-9-48-229 /]# docker exec -it redis-1 /bin/sh /data # redis-cli --cluster create 172.38.0.11:6379 172.38.0.12:6379 \ > 172.38.0.13:6379 172.38.0.14:6379 172.38.0.15:6379 \不要前面的尖括号 > 172.38.0.16:6379 --cluster-replicas 1 不要前面的尖括号 >>> Performing hash slots allocation on 6 nodes... Master[0] -> Slots 0 - 5460 Master[1] -> Slots 5461 - 10922 Master[2] -> Slots 10923 - 16383 Adding replica 172.38.0.15:6379 to 172.38.0.11:6379 Adding replica 172.38.0.16:6379 to 172.38.0.12:6379 Adding replica 172.38.0.14:6379 to 172.38.0.13:6379 M: 25473413da7c71327c6b03fea2d6ee96736a0759 172.38.0.11:6379 slots:[0-5460] (5461 slots) master M: 2744e22697f4138ae5298e11198efb5f8b993654 172.38.0.12:6379 slots:[5461-10922] (5462 slots) master M: a9e53fc5b4c2a526e0f589548236d3f05e3913d1 172.38.0.13:6379 slots:[10923-16383] (5461 slots) master S: b740f3099a7e4087bcef77bc0696b48d5ccee1dc 172.38.0.14:6379 replicates a9e53fc5b4c2a526e0f589548236d3f05e3913d1 S: a4bc0a705ffc5745332581f1fe4f563cfced51b4 172.38.0.15:6379 replicates 25473413da7c71327c6b03fea2d6ee96736a0759 S: 90477b979e90ec3ce96abb4c5e5b768fba81c4fb 172.38.0.16:6379 replicates 2744e22697f4138ae5298e11198efb5f8b993654 Can I set the above configuration? (type 'yes' to accept): yes >>> Nodes configuration updated >>> Assign a different config epoch to each node >>> Sending CLUSTER MEET messages to join the cluster Waiting for the cluster to join .... >>> Performing Cluster Check (using node 172.38.0.11:6379) M: 25473413da7c71327c6b03fea2d6ee96736a0759 172.38.0.11:6379 slots:[0-5460] (5461 slots) master 1 additional replica(s) M: 2744e22697f4138ae5298e11198efb5f8b993654 172.38.0.12:6379 slots:[5461-10922] (5462 slots) master 1 additional replica(s) S: b740f3099a7e4087bcef77bc0696b48d5ccee1dc 172.38.0.14:6379 slots: (0 slots) slave replicates a9e53fc5b4c2a526e0f589548236d3f05e3913d1 M: a9e53fc5b4c2a526e0f589548236d3f05e3913d1 172.38.0.13:6379 slots:[10923-16383] (5461 slots) master 1 additional replica(s) S: 90477b979e90ec3ce96abb4c5e5b768fba81c4fb 172.38.0.16:6379 slots: (0 slots) slave replicates 2744e22697f4138ae5298e11198efb5f8b993654 S: a4bc0a705ffc5745332581f1fe4f563cfced51b4 172.38.0.15:6379 slots: (0 slots) slave replicates 25473413da7c71327c6b03fea2d6ee96736a0759 [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered. /data #
测试(主从复制)
/data # redis-cli -c 127.0.0.1:6379> cluster info cluster_state:ok cluster_slots_assigned:16384 cluster_slots_ok:16384 cluster_slots_pfail:0 cluster_slots_fail:0 cluster_known_nodes:6 cluster_size:3 cluster_current_epoch:6 cluster_my_epoch:1 cluster_stats_messages_ping_sent:412 cluster_stats_messages_pong_sent:423 cluster_stats_messages_sent:835 cluster_stats_messages_ping_received:418 cluster_stats_messages_pong_received:412 cluster_stats_messages_meet_received:5 cluster_stats_messages_received:835 127.0.0.1:6379> cluster nodes 2744e22697f4138ae5298e11198efb5f8b993654 172.38.0.12:6379@16379 master - 0 1599792899841 2 connected 5461-10922 25473413da7c71327c6b03fea2d6ee96736a0759 172.38.0.11:6379@16379 myself,master - 0 1599792900000 1 connected 0-5460 b740f3099a7e4087bcef77bc0696b48d5ccee1dc 172.38.0.14:6379@16379 slave a9e53fc5b4c2a526e0f589548236d3f05e3913d1 0 1599792900000 4 connected a9e53fc5b4c2a526e0f589548236d3f05e3913d1 172.38.0.13:6379@16379 master - 0 1599792899540 3 connected 10923-16383 90477b979e90ec3ce96abb4c5e5b768fba81c4fb 172.38.0.16:6379@16379 slave 2744e22697f4138ae5298e11198efb5f8b993654 0 1599792900542 6 connected a4bc0a705ffc5745332581f1fe4f563cfced51b4 172.38.0.15:6379@16379 slave 25473413da7c71327c6b03fea2d6ee96736a0759 0 1599792899540 5 connected 127.0.0.1:6379> set a b -> Redirected to slot [15495] located at 172.38.0.13:6379 OK ----->去将 172.38.0.13 对应的 redis-3 停掉 (docker stop 34e7ddf06f3b) 172.38.0.13:6379> get a Error: Operation timed out /data # redis-cli -c 127.0.0.1:6379> get a -> Redirected to slot [15495] located at 172.38.0.14:6379 "b" 172.38.0.14:6379> cluster nodes 90477b979e90ec3ce96abb4c5e5b768fba81c4fb 172.38.0.16:6379@16379 slave 2744e22697f4138ae5298e11198efb5f8b993654 0 1599793402383 6 connected b740f3099a7e4087bcef77bc0696b48d5ccee1dc 172.38.0.14:6379@16379 myself,master - 0 1599793402000 7 connected 10923-16383 25473413da7c71327c6b03fea2d6ee96736a0759 172.38.0.11:6379@16379 master - 0 1599793402000 1 connected 0-5460 2744e22697f4138ae5298e11198efb5f8b993654 172.38.0.12:6379@16379 master - 0 1599793401882 2 connected 5461-10922 a4bc0a705ffc5745332581f1fe4f563cfced51b4 172.38.0.15:6379@16379 slave 25473413da7c71327c6b03fea2d6ee96736a0759 0 1599793402000 5 connected a9e53fc5b4c2a526e0f589548236d3f05e3913d1 172.38.0.13:6379@16379 master,fail - 1599793229967 1599793228000 3 connected 172.38.0.14:6379>
SpringBoot微服务打包Docker镜像
新建SpringBoot项目
package com.example.demo.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @RequestMapping("/hello") public String hello(){ return "hello,xxx"; } }
http://localhost:8080/hello 访问没问题
项目中新建Dockerfile(Idea中装docker插件这里仅仅是为了Dockerfile高亮?)
FROM java:8 COPY *.jar /app.jar CMD ["--server.port=8080"] EXPOSE 8080 ENTRYPOINT ["java","-jar","/app.jar"]
服务器新建目录 并 上传内容
[root@10-9-48-229 idea]# pwd /home/idea [root@10-9-48-229 idea]# ls demo-0.0.1-SNAPSHOT.jar Dockerfile
编译镜像 启动 访问
docker build -t chhh192 . # 编译镜像 docker images # 查看镜像 docker run -d -P --name chhh-springboot-web chhh192 #用镜像启动容器 docker ps # 查看端口 curl localhost:32773/hello # 访问 这种黑窗访问方式 不需要开端口 测试方便