感谢狂神的分享。附上B站视频链接。
https://www.bilibili.com/video/BV1og4y1q7M4?from=search&seid=9225650362300658796&spm_id_from=333.337.0.0
Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从 Apache2.0 协议开源。
Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。
容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app),更重要的是容器性能开销极低。
Docker 从 17.03 版本之后分为 CE(Community Edition: 社区版) 和 EE(Enterprise Edition: 企业版),我们用社区版就可以了。官网:https://docs.docker.com/
Web 应用的自动化打包和发布。
自动化测试和持续集成、发布。
在服务型环境中部署和调整数据库或其他的后台应用。
从头编译或者扩展现有的 OpenShift 或 Cloud Foundry 平台来搭建自己的 PaaS 环境。
Docker 是一个用于开发,交付和运行应用程序的开放平台。Docker 使您能够将应用程序与基础架构分开,从而可以快速交付软件。借助 Docker,您可以与管理应用程序相同的方式来管理基础架构。通过利用 Docker 的方法来快速交付,测试和部署代码,您可以大大减少编写代码和在生产环境中运行代码之间的延迟。
1、快速,一致地交付您的应用程序。Docker 允许开发人员使用您提供的应用程序或服务的本地容器在标准化环境中工作,从而简化了开发的生命周期。
容器非常适合持续集成和持续交付(CI / CD)工作流程,请考虑以下示例方案:
您的开发人员在本地编写代码,并使用 Docker 容器与同事共享他们的工作。
他们使用 Docker 将其应用程序推送到测试环境中,并执行自动或手动测试。
当开发人员发现错误时,他们可以在开发环境中对其进行修复,然后将其重新部署到测试环境中,以进行测试和验证。
测试完成后,将修补程序推送给生产环境,就像将更新的镜像推送到生产环境一样简单。
2、响应式部署和扩展
Docker 是基于容器的平台,允许高度可移植的工作负载。Docker 容器可以在开发人员的本机上,数据中心的物理或虚拟机上,云服务上或混合环境中运行。
Docker 的可移植性和轻量级的特性,还可以使您轻松地完成动态管理的工作负担,并根据业务需求指示,实时扩展或拆除应用程序和服务。
3、在同一硬件上运行更多工作负载
Docker 轻巧快速。它为基于虚拟机管理程序的虚拟机提供了可行、经济、高效的替代方案,因此您可以利用更多的计算能力来实现业务目标。Docker 非常适合于高密度环境以及中小型部署,而您可以用更少的资源做更多的事情。
虚拟化技术特点:1.资源占用多 2.冗余步骤多 3.启动很慢
容器化技术:容器化技术不是模拟的一个完整的操作系统
比较Docker和虚拟机的不同:
1.传统虚拟机,虚拟出硬件,运行一个完整的操作系统,然后在这个系统上安装和运行软件。
2.Docker容器内的应用直接运行在宿主机的内核,容器是没有自己的内核的,也没有虚拟硬件。
3.每个容器都是相互隔离的,每个容器都有属于自己的文件系统,互不影响。
容器化带来的好处:
Docker的基本组成图如下:
说明:
Install on CentOS
#安装gcc相关环境
yum -y install gcc
yum-y install gcc-c++
#1、卸载旧的版本
yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
#2、需要的安装包
yum install -y yum-utils
#3、设置阿里云镜像的仓库
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
#更新yum软件包索引
yum makecache fast
#4、安装 docker-ce 社区版
yum install -y docker-ce docker-ce-cli containerd.io
#5、启动
systemctl start docker
#6、检查版本信息
docker version
#7、hello-world
docker run hello-world
#8、查看镜像
docker images
#学会了安装,了解一下卸载。 卸载依赖 删除文件夹
yum remove docker-ce docker-ce-cli containerd.io
rm -rf /var/lib/docker
rm -rf /var/lib/containerd
如果不行,手动删除 https://www.cnblogs.com/kingsonfu/p/11582495.html
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://fz6njoj3.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
Docker是一个Client-Server结构的系统,Docker的守护进程运行在主机上,通过Socker从客户端访问!Docker Server接收到Docker-Client的指令,就会执行这个指令!
Docker为什么比VM Ware快?
1、Docker比虚拟机更少的抽象层
2、docker利用宿主机的内核,VM需要的是Guest OS
Docker新建一个容器的时候,不需要像虚拟机一样重新加载一个操作系统内核,直接利用宿主机的操作系统,而虚拟机是需要加载Guest OS。
docker version #查看docker的版本信息
docker info #查看docker的系统信息,包括镜像和容器的数量
docker 命令 --help #帮助命令(可查看可选的参数)
docker COMMAND --help
命令的帮助文档地址:https://docs.docker.com/engine/reference/commandline/docker/
[root@wangjingkai lib]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest feb5d9fea6a5 5 months ago 13.3kB
#解释:
1.REPOSITORY 镜像的仓库源
2.TAG 镜像的标签
3.IMAGE ID 镜像的id
4.CREATED 镜像的创建时间
5.SIZE 镜像的大小
# 可选参数
-a/--all 列出所有镜像
-q/--quiet 只显示镜像的id
#可选参数
Search the Docker Hub for images
Options:
-f, --filter filter Filter output based on conditions provided
--format string Pretty-print search using a Go template
--limit int Max number of search results (default 25)
--no-trunc Don't truncate output
#搜索收藏数大于3000的镜像
[root@iZwz99sm8v95sckz8bd2c4Z ~]# docker search mysql --filter=STARS=3000
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
mysql MySQL is a widely used, open-source relation… 10308 [OK]
mariadb MariaDB is a community-developed fordockerk of MyS… 3819 [OK]
[root@iZwz99sm8v95sckz8bd2c4Z ~]# docker pull mysql
Using default tag: latest #如果不写tag默认就是latest
latest: Pulling from library/mysql
6ec7b7d162b2: Pull complete #分层下载,docker image的核心-联合文件系统
fedd960d3481: Pull complete
7ab947313861: Pull complete
64f92f19e638: Pull complete
3e80b17bff96: Pull complete
014e976799f9: Pull complete
59ae84fee1b3: Pull complete
ffe10de703ea: Pull complete
657af6d90c83: Pull complete
98bfb480322c: Pull complete
6aa3859c4789: Pull complete
1ed875d851ef: Pull complete
Digest: sha256:78800e6d3f1b230e35275145e657b82c3fb02a27b2d8e76aac2f5e90c1c30873 #签名
Status: Downloaded newer image for mysql:latest
docker.io/library/mysql:latest #下载来源的真实地址 #docker pull mysql等价于docker pull docker.io/library/mysql:latest
[root@iZwz99sm8v95sckz8bd2c4Z ~]# docker pull mysql:5.7
5.7: Pulling from library/mysql
6ec7b7d162b2: Already exists
fedd960d3481: Already exists
7ab947313861: Already exists
64f92f19e638: Already exists
3e80b17bff96: Already exists
014e976799f9: Already exists
59ae84fee1b3: Already exists
7d1da2a18e2e: Pull complete
301a28b700b9: Pull complete
529dc8dbeaf3: Pull complete
bc9d021dc13f: Pull complete
Digest: sha256:c3a567d3e3ad8b05dfce401ed08f0f6bf3f3b64cc17694979d5f2e5d78e10173
Status: Downloaded newer image for mysql:5.7
docker.io/library/mysql:5.7
#1.删除指定的镜像id
[root@iZwz99sm8v95sckz8bd2c4Z ~]# docker rmi -f 镜像id
#2.删除多个镜像id
[root@iZwz99sm8v95sckz8bd2c4Z ~]# docker rmi -f 镜像id 镜像id 镜像id
#3.删除全部的镜像id
[root@iZwz99sm8v95sckz8bd2c4Z ~]# docker rmi -f $(docker images -aq)
说明:我们有了镜像才可以创建容器, 下载一个centos镜像来测试学习
docker pull centos
docker run [可选参数] image
#参数说明
--name="名字" 指定容器名字
-d 后台方式运行
-it 使用交互方式运行,进入容器查看内容
-p 指定容器的端口
(
-p ip:主机端口:容器端口 配置主机端口映射到容器端口
-p 主机端口:容器端口
-p 容器端口
)
-P 随机指定端口(大写的P)
[root@wangjingkai ~]# docker run -it centos /bin/bash
[root@1f90b860e9a0 /]# ls
bin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var
#exit 停止并退出容器(后台方式运行则仅退出)
#Ctrl+P+Q 不停止容器退出 大写需要打开
[root@wangjingkai ~]# docker run -it centos /bin/bash
[root@1f90b860e9a0 /]# ls
bin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var
[root@1f90b860e9a0 /]# exit
exit
[root@wangjingkai ~]# docker run -it centos /bin/bash
[root@07a8ecf21ce3 /]# [root@wangjingkai ~]# docker run -it centos /bin/bash
#docker ps
# 列出当前正在运行的容器
-a # 列出所有容器的运行记录
-n=? # 显示最近创建的n个容器
-q # 只显示容器的编号
[root@wangjingkai ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
07a8ecf21ce3 centos "/bin/bash" About a minute ago Up About a minute sweet_almeida
[root@wangjingkai ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c9c60a05d726 centos "/bin/bash" 42 seconds ago Exited (0) 39 seconds ago strange_bouman
07a8ecf21ce3 centos "/bin/bash" About a minute ago Up About a minute sweet_almeida
1f90b860e9a0 centos "/bin/bash" 2 minutes ago Exited (0) About a minute ago xenodochial_robinson
31d11c1fb31e centos "/bin/bash" 6 minutes ago Exited (0) 5 minutes ago nice_leavitt
docker rm 容器id #删除指定的容器,不能删除正在运行的容器,强制删除使用 rm -f
docker rm -f $(docker ps -aq) #删除所有的容器
docker ps -a -q|xargs docker rm #删除所有的容器
docker start 容器id #启动容器
docker restart 容器id #重启容器
docker stop 容器id #停止当前运行的容器
docker kill 容器id #强制停止当前容器
#命令 docker run -d 镜像名
#问题 docker ps,发现 centos 停止了
#常见的坑:: docker容器使用后台运行,就必须要有要一个前台进程,doqker发现没有应用,就会自动停止
[root@wangjingkai ~]# docker run -d centos
b570968cad70ce6d75af86585efa9697c16beac62423437b9917b04b4b915eda
[root@wangjingkai ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
#编写shell脚本循环执行,使得centos容器保持运行状态
[root@wangjingkai ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
centos latest 5d0da3dc9764 5 months ago 231MB
[root@wangjingkai ~]# docker run -d centos /bin/sh -c "while true;do echo wangjingkai;sleep 1;done;"
d9c1a0d0fa372fd2eec14a0fef629cc017791776d873d060850468b6ab414115
[root@wangjingkai ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d9c1a0d0fa37 centos "/bin/sh -c 'while t…" 3 seconds ago Up 3 seconds competent_leavitt
#显示日志
-t #时间戳
-f #跟随最近的日志打印
--tail number #要显示的行数 最后[number]行
[root@wangjingkai ~]# docker logs -tf --tail 10 d9c1a0d0fa37
2022-03-09T08:10:32.877583150Z wangjingkai
2022-03-09T08:10:33.880475447Z wangjingkai
2022-03-09T08:10:34.883497238Z wangjingkai
2022-03-09T08:10:35.886207215Z wangjingkai
2022-03-09T08:10:36.889473108Z wangjingkai
2022-03-09T08:10:37.892314077Z wangjingkai
2022-03-09T08:10:38.895312541Z wangjingkai
2022-03-09T08:10:39.898172307Z wangjingkai
2022-03-09T08:10:40.901211128Z wangjingkai
2022-03-09T08:10:41.904595609Z wangjingkai
2022-03-09T08:10:42.907655169Z wangjingkai
2022-03-09T08:10:43.910632377Z wangjingkai
#命令 docker top 容器id
[root@wangjingkai ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d9c1a0d0fa37 centos "/bin/sh -c 'while t…" 7 minutes ago Up 7 minutes competent_leavitt
#UID 用户id PID 进程id PPID 父进程ID
[root@wangjingkai ~]# docker top d9c1a0d0fa37
UID PID PPID C STIME
root 8189 8171 0 16:09
root 9120 8189 0 16:17
#命令 dokcer inspect 容器id
#测试
[root@wangjingkai ~]# docker inspect d9c1a0d0fa37
[
{
"Id": "d9c1a0d0fa372fd2eec14a0fef629cc017791776d873d060850468b6ab414115",
"Created": "2022-03-09T08:09:51.388191458Z",
"Path": "/bin/sh",
"Args": [
"-c",
"while true;do echo wangjingkai;sleep 1;done;"
],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 8189,
"ExitCode": 0,
"Error": "",
"StartedAt": "2022-03-09T08:09:51.762065224Z",
"FinishedAt": "0001-01-01T00:00:00Z"
},
"Image": "sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6",
"ResolvConfPath": "/var/lib/docker/containers/d9c1a0d0fa372fd2eec14a0fef629cc017791776d873d060850468b6ab414115/resolv.conf",
"HostnamePath": "/var/lib/docker/containers/d9c1a0d0fa372fd2eec14a0fef629cc017791776d873d060850468b6ab414115/hostname",
"HostsPath": "/var/lib/docker/containers/d9c1a0d0fa372fd2eec14a0fef629cc017791776d873d060850468b6ab414115/hosts",
"LogPath": "/var/lib/docker/containers/d9c1a0d0fa372fd2eec14a0fef629cc017791776d873d060850468b6ab414115/d9c1a0d0fa372fd2eec14a0fef629cc017791776d873d060850468b6ab414115-json.log",
"Name": "/competent_leavitt",
"RestartCount": 0,
"Driver": "overlay2",
"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,
"CgroupnsMode": "host",
"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": {
"LowerDir": "/var/lib/docker/overlay2/3bb0b75b11c836eec81100afd5e543763bea131e37b7b270494413e1c0812c54-init/diff:/var/lib/docker/overlay2/b8f39ac06260a61c2029e005d14540939852fa7b95ff70bb20637375edd30060/diff",
"MergedDir": "/var/lib/docker/overlay2/3bb0b75b11c836eec81100afd5e543763bea131e37b7b270494413e1c0812c54/merged",
"UpperDir": "/var/lib/docker/overlay2/3bb0b75b11c836eec81100afd5e543763bea131e37b7b270494413e1c0812c54/diff",
"WorkDir": "/var/lib/docker/overlay2/3bb0b75b11c836eec81100afd5e543763bea131e37b7b270494413e1c0812c54/work"
},
"Name": "overlay2"
},
"Mounts": [],
"Config": {
"Hostname": "d9c1a0d0fa37",
"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 wangjingkai;sleep 1;done;"
],
"Image": "centos",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": {
"org.label-schema.build-date": "20210915",
"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": "2e2eb59de84883f4e7967b6b4e61921bba9f2774280b28acf1872f7942384795",
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"Ports": {},
"SandboxKey": "/var/run/docker/netns/2e2eb59de848",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "6f38e9d756041c8f5c4cbd70e7c81753831e78c2525f2b953f3bc66c0225e46f",
"Gateway": "172.18.0.1",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "172.18.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"MacAddress": "02:42:ac:12:00:02",
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "7f9e905853e256d73202907a925d8057bd9279cab2c98a973f27badba252c1d1",
"EndpointID": "6f38e9d756041c8f5c4cbd70e7c81753831e78c2525f2b953f3bc66c0225e46f",
"Gateway": "172.18.0.1",
"IPAddress": "172.18.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:12:00:02",
"DriverOpts": null
}
}
}
}
]
#我们通常容器都是使用后台方式运行的,需要进入容器,修改一些配置。
#命令
#方式1
docker exec -it 容器id bashShell
[root@wangjingkai ~]# docker exec -it d9c1a0d0fa37 /bin/bash
[root@d9c1a0d0fa37 /]# ls
bin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var
#方式2
docker attch 容器id
[root@wangjingkai ~]# docker attach d9c1a0d0fa37
#docker exec 进入容器后开启一个新的终端,可以在里面操作
#docker attach 进入容器正在执行的终端,不会启动新的进程。
docker cp 容器id: 容器内路径 目的的主机路径
#查看当前主机目录下的文件
[root@wangjingkai home]# ls
admin apache-tomcat-9.0.52 hilde swap wjk
[root@wangjingkai home]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
[root@wangjingkai home]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fece35d969e5 centos "/bin/bash" 5 minutes ago Exited (127) 3 minutes ago nice_carver
[root@wangjingkai home]# docker start fece35d969e5
fece35d969e5
[root@wangjingkai home]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fece35d969e5 centos "/bin/bash" 5 minutes ago Up 4 seconds nice_carver
#进入容器内部
[root@wangjingkai home]# docker attach fece35d969e5
[root@fece35d969e5 /]# cd /home
[root@fece35d969e5 home]# ls
test.java
#在容器内部新建一个文件
[root@fece35d969e5 home]# touch wang.java
[root@fece35d969e5 home]# exit
exit
[root@wangjingkai home]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
[root@wangjingkai home]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fece35d969e5 centos "/bin/bash" 6 minutes ago Exited (0) 13 seconds ago nice_carver
#从容器内拷贝文件到主机上
[root@wangjingkai home]# docker cp fece35d969e5:/home/wang.java /home
[root@wangjingkai home]# ls
admin apache-tomcat-9.0.52 hilde swap wang.java wjk
#拷贝是一个手动过程,未来我们使用 -v 卷 的技术,可以实现 自动同步。
attach Attach to a running container #当前shell下attach连接指定运行镜像
build Build an image from a Dockerfile #通过Dockerfile定制镜像
commit Create a new image from a container's changes #提交当前容器为新的镜像
cp Copy files/folders from a container to a HOSTDIR or to STDOUT #从容器中拷贝指定文件或者目录到宿主机中
create Create a new container #创建一个新的容器,同run 但不启动容器
diff Inspect changes on a container's filesystem #查看docker容器变化
events Get real time events from the server#从docker服务获取容器实时事件
exec Run a command in a running container#在已存在的容器上运行命令
export Export a container's filesystem as a tar archive #导出容器的内容流作为一个tar归档文件(对应import)
history Show the history of an image #展示一个镜像形成历史
images List images #列出系统当前镜像
import Import the contents from a tarball to create a filesystem image #从tar包中的内容创建一个新的文件系统映像(对应export)
info Display system-wide information #显示系统相关信息
inspect Return low-level information on a container or image #查看容器详细信息
kill Kill a running container #kill指定docker容器
load Load an image from a tar archive or STDIN #从一个tar包中加载一个镜像(对应save)
login Register or log in to a Docker registry#注册或者登陆一个docker源服务器
logout Log out from a Docker registry #从当前Docker registry退出
logs Fetch the logs of a container #输出当前容器日志信息
pause Pause all processes within a container#暂停容器
port List port mappings or a specific mapping for the CONTAINER #查看映射端口对应的容器内部源端口
ps List containers #列出容器列表
pull Pull an image or a repository from a registry #从docker镜像源服务器拉取指定镜像或者库镜像
push Push an image or a repository to a registry #推送指定镜像或者库镜像至docker源服务器
rename Rename a container #重命名容器
restart Restart a running container #重启运行的容器
rm Remove one or more containers #移除一个或者多个容器
rmi Remove one or more images #移除一个或多个镜像(无容器使用该镜像才可以删除,否则需要删除相关容器才可以继续或者-f强制删除)
run Run a command in a new container #创建一个新的容器并运行一个命令
save Save an image(s) to a tar archive#保存一个镜像为一个tar包(对应load)
search Search the Docker Hub for images #在docker
hub中搜索镜像
start Start one or more stopped containers#启动容器
stats Display a live stream of container(s) resource usage statistics #统计容器使用资源
stop Stop a running container #停止容器
tag Tag an image into a repository #给源中镜像打标签
top Display the running processes of a container #查看容器中运行的进程信息
unpause Unpause all processes within a container #取消暂停容器
version Show the Docker version information#查看容器版本号
wait Block until a container stops, then print its exit code #截取容器停止时的退出状态值
#1、搜索镜像
docker search nginx
#2、拉取镜像
docker pull nginx
#3、启动
# -d 后台运行 --name 起名 -p 3344:80 将宿主机的端口3334映射到该容器的80端口
docker run -d --name nginx01 -p 3344:80 nginx
#测试
[root@wangjingkai curl-7.56.1]# curl localhost:3344
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
[root@wangjingkai curl-7.56.1]#
注意:如果提示-bash: curl: command not found
就是没安装curl :解决方案 安装即可。
csdn推荐 :https://blog.csdn.net/sqlquan/article/details/106105065?spm=1001.2101.3001.6650.3&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-3.pc_relevant_antiscan_v2&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-3.pc_relevant_antiscan_v2&utm_relevant_index=6
小细节:我是通过地址下载到本地电脑上,xshell,rz 上传到服务器里,在进行安装的.注意要进入目录执行 ./ 命令。
端口暴露的概念
思考问题:我们每次改动nginx配置文件,都需要进入容器内部 ? 十分的麻烦,要是可以在容器外部提供一个映射路径,达到在容器外部修改文件,容器内部就可以自动修改? -v 数据卷
#1、拉取 tomcat
docker pull tomcat
#2、启动运行
docker run -d -p 3355:8080 --name tomcat01 tomcat
#3、测试访问没问题,但是没有页面
#发现问题,1、linux命令少了 2、webpps目录为空,因为阿里云镜像的缘故,它默认是最小的镜像,所有不必要的都会被剔除掉,保证最小的可运行环境。
#解决办法:把webapps.dist 里的文件 复制到 webapps 下
cp -r webapps.dist/* webapps
添加 ’-e ES_JAVA_OPTS=“-Xms128m -Xmx512m” ‘ 配置ElasticSearch的虚拟机占用的内存大小。
$ docker run -d --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms128m -Xmx512m" elasticsearch:7.6.2
Portaniner是Docker的图形化管理工具,类似的工具还有Rancher(CI/CD再用)
下载运行Portaniner镜像并运行,设置本机映射端口为8088
[root@wangjingkai ~]# docker run -d -p 8088:9000 --restart=always -v /var/run/docker.sock:/var/run/docker.sock --privileged=true portainer/portainer
镜像是一种轻量级、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需要的所有内容,包括代码,运行时(一个程序在运行或者在被执行的依赖)、库,环境变量和配置文件。
使用docker commit 命令提交容器成为一个新的版本
docker commit -m=“提交的描述信息” -a="作者" 容器id 目标镜像名:[TAG]
测试
#1、启动一个默认的tomcat
[root@wangjingkai ~]# docker run -d -p 8080:8080 --name tomcat02 tomcat
#2、发现这个默认的tomcat是没有webapps应用,镜像的原因,官方的镜像默认 webapps下面是没有文件的!
#3、我自己拷贝进去了基本的文件
[root@wangjingkai ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
05129a70a3a2 tomcat "catalina.sh run" About a minute ago Up About a minute 0.0.0.0:8080->8080/tcp tomcat02
8975a1f21c6e portainer/portainer "/portainer" 40 minutes ago Up 40 minutes 0.0.0.0:8088->9000/tcp bold_feynman
34d94a481c82 nginx "/docker-entrypoint.…" 5 hours ago Up 5 hours 0.0.0.0:3344->80/tcp nginx01
[root@wangjingkai ~]# docker exec -it 05129a70a3a2 /bin/bash
root@05129a70a3a2:/usr/local/tomcat# ls
BUILDING.txt LICENSE README.md RUNNING.txt conf logs temp webapps.dist
CONTRIBUTING.md NOTICE RELEASE-NOTES bin lib native-jni-lib webapps work
root@05129a70a3a2:/usr/local/tomcat# cp -r webapps.dist/* webapps
#4、将我们操作过的容器通过commit提交为一个镜像!我们以后就使用我们修改过的镜像即可,这就是我们自己的一个修改的镜像
[root@wangjingkai ~]# docker commit -a="wangjingkai" -m"add webapps app" 05129a70a3a2 tomcat02:1.0
docker的理念回顾
总结:容器的持久化和同步操作,容器间也可以数据共享的。
方式一:直接使用命令来挂载 -v
#docker run -it -v 主机目录:容器内目录
#测试
[root@wangjingkai ~]# docker run -it -v /home/ceshi:/home centos /bin/bash
[root@d23be47d3c54 /]#
这个时候容器外的/home/ceshi 目录 就和容器内 /home 目录进行了同步绑定,数据会一致。无论是里面还是外面新增数据。都会同步。删除文件也会同步。
测试删除容器,外面的数据是否还在。依旧存在。
#获取镜像
[root@wangjingkai ceshi]# docker pull mysql:5.7
#运行容器,需要做数据挂载!# 安装启动mysq1 ,需要配置密码的,这是要注意点!
#官方配置启动mysqk
#这里是my-secret-pw 是后面docker secret所学
$ docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag
#启动我们的
-d #后台运行
-p #端口映射
-v #数据卷挂载
-e #环境配置
--name #容器名字
[root@wangjingkai ~]# 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=root --name mysql01 mysql:5.7
#启动成功之后,用Navicat 测试连接成功。
#navicat -- 通过主机名,账号,密码,端口 连接到服务器。 端口3310 映射 服务器的3306.
# 匿名挂载
-v 容器内目录
-P 大写的P是随机映射端口
docker run -d -P --name nginx01 -v /etc/nginx nginx
#docker volume --help
[root@wangjingkai ~]# docker volume --help
Usage: docker volume COMMAND
Manage volumes
Commands:
create Create a volume
inspect Display detailed information on one or more volumes
ls List volumes
prune Remove all unused local volumes
rm Remove one or more volumes
Run 'docker volume COMMAND --help' for more information on a command.
# 查看所有 volume卷 的情况
[root@wangjingkai ~]# docker volume ls
DRIVER VOLUME NAME
local 789c3bb335a0bc47e65ccc8c684b495883bf02a4ba1177af4986ca9e90119aef
这种就是匿名挂载。在 -v 的时候只写了内部地址。
#具名挂载
#通过 -v 卷名:容器内地址
[root@wangjingkai ~]# docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx nginx
4ed26bc6b0a782bd66d7898334a72a85bc0c3da88c9e6acfb9d997b61d96c32a
[root@wangjingkai ~]# docker volume ls
DRIVER VOLUME NAME
local juming-nginx
#查看一下这个卷
[root@wangjingkai ~]# docker volume inspect juming-nginx
[
{
"CreatedAt": "2022-03-10T16:30:37+08:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/juming-nginx/_data", #路径
"Name": "juming-nginx",
"Options": null,
"Scope": "local"
}
]
所有的docker容器内的卷,没有指定目录的情况下都是在/var/lib/docker/volumes/xxxx
我们通过具名挂载可以方便的找到我们的一个卷,大多数情况在使用的具名挂载
Dockerfile 就是用来构建docker镜像的构建文件! 命令脚本
通过这个脚本可以生成镜像,镜像是一层一层,脚本就是一个一个命令,每个命令就是一层。
#编写dockerfile
[root@wangjingkai docker-test-volume]# vim dockerfile1
#编写指令
FROM centos
VOLUME ["volume01","volume02"]
CMD echo "----end-----"
CMD /bin/bash
#查看
[root@wangjingkai docker-test-volume]# cat dockerfile1
FROM centos
VOLUME ["volume01,"volume02""] #匿名挂载
CMD echo "----end-----"
CMD /bin/bash
#每个命令就是一层。
#通过自己写的镜像启动容器
[root@wangjingkai docker-test-volume]# docker run -it 4cdffc814ce2 /bin/bash
生成一个镜像,在创建的时候挂载出来。这个就存在一个和外部同步的目录。
查看一下卷挂载的路径
同步成功。
这种方式我们未来使用的十分多,因为我们通常会构建自己的镜像!
假设构建镜像时候没有挂载卷,要手动挂载 -v 卷名:容器内地址
、
# --volumes--from
数据是共享的且是拷贝的概念。
dockerfile 是用来构建docker镜像的文件。命令参数脚本。
构建步骤:
1、编写一个dockerfile文件
2、docker build 构建成为一个镜像
3、docker run 运行镜像
4、docker push 发布镜像(DockerHub , 阿里云镜像仓库)
#命令都需要大写,但是我看着不舒服,大写不认识单词,小写方便记录。上面是正确的。
#示例
from centos:7
maintainer "[email protected]"
run mkdir -p /usr/local/java && mkdir -p /usr/local/tomcat
add jdk-11.0.11_linux-x64_bin.tar.gz /usr/local/java
workdir /usr/local
volume
expose 8080
cmd ["/usr/local/tomcat/apache-tomcat-9.0.46/bin/catalina.sh","run"]
entrypoint
onbuild
copy
env JAVA_HOME /usr/local/java/jdk-11.0.11/
env PATH $PATH:$JAVA_HOME/bin
# 使用标准openjdk环境
FROM openjdk:latest
# dockerfile build 环境变量 按需使用
ARG NAME=test-docker-image
ARG VERSION=0.0.1
ARG PROFILES=test
# 项目打包后的包名
ENV JAR_FILE=test-server-0.0.1-SNAPSHOT.jar
# 项目服务目录
ENV PROJECT_DIR=project
# 服务根目录
ENV ROOT_DIR=/home/testserver
#在容器运行时声明一个 volume, 在容器中创建目录
VOLUME $ROOT_DIR
VOLUME /usr/local/src/soft/template
# 添加镜像属性
LABEL name=$NAME version=$VERSION email="[email protected]"
# maven 构建项目 先构建项目jar包,然后用copy或add将jar包导入镜像,是两步操作
# 导入 JAR 到镜像中的指定目录
COPY target/$JAR_FILE $ROOT_DIR
# Create a script 由于ENTRYPOINT无法使用dockerfile中定义的环境变量
# 所以需要按照指定的环境变量生成运行脚本,并输出到文件,之后ENTRYPOINT运行脚本文件就行了
RUN echo "java -Xms1024m -Xmx1024m -jar $ROOT_DIR/$JAR_FILE --spring.profiles.active=$PROFILES" > /run_module.sh
# run 执行命令
ENTRYPOINT ["/bin/bash", "/run_module.sh"]
# 指定默认暴露端口, 这样在容器运行时可以知道应该映射哪些端口
EXPOSE 8888
创建一个自己的 centos
#1、编写dockerfile文件
FROM centos:7
MAINTAINER wangjingkai<302658980@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
#2、通过这个文件构建镜像
#docker build -f dockerfile路径名 -t 镜像名:[tag]
原来是centos没有vim编辑,ifconfig
可以列出本例镜像的变更历史
[root@wangjingkai dockerfile]# docker history caa7cc75976e
IMAGE CREATED CREATED BY SIZE COMMENT
caa7cc75976e 3 minutes ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "/bin… 0B
7566ced72bd7 3 minutes ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "echo… 0B
97dec348839b 3 minutes ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "echo… 0B
31ca87252ee3 3 minutes ago /bin/sh -c #(nop) EXPOSE 80 0B
c68fe8901852 3 minutes ago /bin/sh -c yum -y install net-tools 161MB
b58d71b69792 5 minutes ago /bin/sh -c yum -y install vim 216MB
22b78729c30e 7 minutes ago /bin/sh -c #(nop) WORKDIR /usr/local 0B
2b2cd8bae358 7 minutes ago /bin/sh -c #(nop) ENV MYPATH=/usr/local 0B
2ec9c8973b1e 7 minutes ago /bin/sh -c #(nop) MAINTAINER wangjingkai<30… 0B
eeb6ee3f44bd 6 months ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 6 months ago /bin/sh -c #(nop) LABEL org.label-schema.sc… 0B
<missing> 6 months ago /bin/sh -c #(nop) ADD file:b3ebbe8bd304723d4… 204MB
1、准备镜像文件 tomcat 压缩包 ,jdk压缩包
2、编写dockerfile文件、官方命名 Dockerfile ,bulid 会自动寻找这个文件,就不需要-f指定了
FROM centos:7
MAINTAINER wangjingkai<302658980@qq.com>
COPY readme.txt /usr/local/readme.txt
ADD jdk-8u241-linux-x64.tar.gz /usr/local/
ADD apache-tomcat-9.0.52.tar.gz /usr/local/
RUN yum -y install vim
ENV MYPATH /usr/local
WORKDIR $MYPATH
ENV JAVA_HOME /usr/local/jdk1.8.0_241
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.52
ENV CATALINA_BASH /usr/local/apache-tomcat-9.0.52
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CALATINA_HOME/bin
EXPOSE 8080
CMD /usr/local/apache-tomcat-9.0.52/bin/startup.sh && tail -F /usr/local/apache-tomcat-9.0.52/bin/logs/catalina.
out
3、构建镜像
#docker build -t diytomcat . (后面有个点)
4、启动镜像
[root@wangjingkai tomcat]# docker run -d -p 9090:8080 --name wangjingkaitomcat -v /home/wjk/tomcat/test:/usr/local/apache-tomcat-9.0.52/webapps/test -v /home/wjk/tomcat/tomcatlogs/:/usr/local/apache-tomcat-9.0.52/logs diytomcat
5、访问测试
web.xml
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4">
web-app>
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
hello wangjingkai
Hello World!
<%
System.out.println("---my test web log");
%>
上传到dockerhub
1、登陆
[root@wangjingkai ~]# docker login -u wangjingkaidockerhub
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
[root@wangjingkai ~]#
2、上传
#docker push (用户名)/镜像名:tag
docker push wangjingkaidockerhub/tomcat:1.0
3、退出
[root@wangjingkai ~]# docker logout
阿里云镜像服务上
1、登陆阿里云
2、容器镜像服务
3、创建命名空间
4、创建镜像仓库
5、操作
$ docker login --username=原名陌生哦 registry.cn-hangzhou.aliyuncs.com
用于登录的用户名为阿里云账号全名,密码为开通服务时设置的密码。
您可以在访问凭证页面修改凭证密码。
$ docker pull registry.cn-hangzhou.aliyuncs.com/wangjingkai/wangjingkai:[镜像版本号]
$ docker login --username=原名陌生哦 registry.cn-hangzhou.aliyuncs.com
$ docker tag [ImageId] registry.cn-hangzhou.aliyuncs.com/wangjingkai/wangjingkai:[镜像版本号]
$ docker push registry.cn-hangzhou.aliyuncs.com/wangjingkai/wangjingkai:[镜像版本号]
请根据实际镜像信息替换示例中的[ImageId]和[镜像版本号]参数。
从ECS推送镜像时,可以选择使用镜像仓库内网地址。推送速度将得到提升并且将不会损耗您的公网流量。
如果您使用的机器位于VPC网络,请使用 registry-vpc.cn-hangzhou.aliyuncs.com 作为Registry的域名登录。
使用"docker tag"命令重命名镜像,并将它通过专有网络地址推送至Registry。
$ docker imagesREPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZEregistry.aliyuncs.com/acs/agent 0.7-dfb6816 37bb9c63c8b2 7 days ago 37.89 MB$ docker tag 37bb9c63c8b2 registry-vpc.cn-hangzhou.aliyuncs.com/acs/agent:0.7-dfb6816
使用 “docker push” 命令将该镜像推送至远程。
$ docker push registry-vpc.cn-hangzhou.aliyuncs.com/acs/agent:0.7-dfb6816
小结:
[root@wangjingkai ~]# 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 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:16:3e:0b:ec:97 brd ff:ff:ff:ff:ff:ff
inet 172.17.28.181/18 brd 172.17.63.255 scope global dynamic eth0
valid_lft 309500498sec preferred_lft 309500498sec
#docker0 地址
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:ac:04:5a:d5 brd ff:ff:ff:ff:ff:ff
inet 172.18.0.1/16 brd 172.18.255.255 scope global docker0
valid_lft forever preferred_lft forever
3个网络
#问题:docker是如何处理容器网络访问的?
网络模型图
结论:tomcat01和tomcat02是公用的一个路由器 docker0
所有的容器不指定网络的情况下,都是由docker0路由的。docker会给我们的容器分配一个默认可用的ip 0~255
Docker 容器里的网络 核心使用了Linux的虚拟化网络技术,在容器内和docker0分布创建了一个虚拟网卡,通过veth-pair进行连接通信。
Docker 中所有的网络接口都是虚拟的。虚拟的转发效率高。(例如 内网传递的速度就特别快。飞秋)
只要容器删除,对应网桥就没了。
思考一个场景,我们编写了一个微服务,database url=ip:,项目不重启,数据库ip换掉了,我们希望名字来进行访问容器?
通过link
我们自定义的网络docker都已经帮我们维护好了对应的关系,推荐我们平时使用自定义的网络。
好处:
不同的集群使用不同的网络,保证集群是安全和健康的。
连接一个容器到一个网络
docker network connect mynet tomcat01
shell脚本
#创建网卡
docker network create redis --subnet 172.38.0.0/16
#通过脚本创建六个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
#脚本启动
docker run -p 637${port}:6379 -p 1637${port}:16379 --name redis-${port} \
-v /mydata/redis/node-${port}/data:/data \
-v /mydata/redis/node-${port}/conf/redis.conf:/etc/redis/redis.conf \
-d --net redis --ip 172.38.0.1${port} redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf
#启动一个redis
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
#启动第二个redis
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
#启动第三个redis
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
#启动第四个redis
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
#启动第五个redis
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
#启动第六个redis
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-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
/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: 4c5124a0384f5dcc16fc2c46d4d898a00eac2af1 172.38.0.11:6379
slots:[0-5460] (5461 slots) master
M: 0a901487d8ff15de9e41706fbd5bb0bd99e9b052 172.38.0.12:6379
slots:[5461-10922] (5462 slots) master
M: cf80266d6a94f9f200ad0e75824a36e7d00ab1f0 172.38.0.13:6379
slots:[10923-16383] (5461 slots) master
S: c419506c3dfd89bb3c10c9a4e71798c6928841d9 172.38.0.14:6379
replicates cf80266d6a94f9f200ad0e75824a36e7d00ab1f0
S: b4607c2b19afd41f034ab49dac4dd7826d32cd34 172.38.0.15:6379
replicates 4c5124a0384f5dcc16fc2c46d4d898a00eac2af1
S: 0a300109789e90a5713f9b453cf6827e573a4d38 172.38.0.16:6379
replicates 0a901487d8ff15de9e41706fbd5bb0bd99e9b052
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: 4c5124a0384f5dcc16fc2c46d4d898a00eac2af1 172.38.0.11:6379
slots:[0-5460] (5461 slots) master
1 additional replica(s)
S: b4607c2b19afd41f034ab49dac4dd7826d32cd34 172.38.0.15:6379
slots: (0 slots) slave
replicates 4c5124a0384f5dcc16fc2c46d4d898a00eac2af1
S: c419506c3dfd89bb3c10c9a4e71798c6928841d9 172.38.0.14:6379
slots: (0 slots) slave
replicates cf80266d6a94f9f200ad0e75824a36e7d00ab1f0
S: 0a300109789e90a5713f9b453cf6827e573a4d38 172.38.0.16:6379
slots: (0 slots) slave
replicates 0a901487d8ff15de9e41706fbd5bb0bd99e9b052
M: cf80266d6a94f9f200ad0e75824a36e7d00ab1f0 172.38.0.13:6379
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
M: 0a901487d8ff15de9e41706fbd5bb0bd99e9b052 172.38.0.12:6379
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
/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:174
cluster_stats_messages_pong_sent:175
cluster_stats_messages_sent:349
cluster_stats_messages_ping_received:170
cluster_stats_messages_pong_received:174
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:349
127.0.0.1:6379> cluster nodes
b4607c2b19afd41f034ab49dac4dd7826d32cd34 172.38.0.15:6379@16379 slave 4c5124a0384f5dcc16fc2c46d4d898a00eac2af1 0 1648627924537 5 connected
c419506c3dfd89bb3c10c9a4e71798c6928841d9 172.38.0.14:6379@16379 slave cf80266d6a94f9f200ad0e75824a36e7d00ab1f0 0 1648627923033 4 connected
0a300109789e90a5713f9b453cf6827e573a4d38 172.38.0.16:6379@16379 slave 0a901487d8ff15de9e41706fbd5bb0bd99e9b052 0 1648627924537 6 connected
cf80266d6a94f9f200ad0e75824a36e7d00ab1f0 172.38.0.13:6379@16379 master - 0 1648627924000 3 connected 10923-16383
0a901487d8ff15de9e41706fbd5bb0bd99e9b052 172.38.0.12:6379@16379 master - 0 1648627924035 2 connected 5461-10922
4c5124a0384f5dcc16fc2c46d4d898a00eac2af1 172.38.0.11:6379@16379 myself,master - 0 1648627923000 1 connected 0-5460
127.0.0.1:6379> set a b
-> Redirected to slot [15495] located at 172.38.0.13:6379
OK
172.38.0.13:6379> get a
"b"
172.38.0.13:6379> get a
^C
/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
c419506c3dfd89bb3c10c9a4e71798c6928841d9 172.38.0.14:6379@16379 myself,master - 0 1648628529000 7 connected 10923-16383
4c5124a0384f5dcc16fc2c46d4d898a00eac2af1 172.38.0.11:6379@16379 master - 0 1648628531534 1 connected 0-5460
0a300109789e90a5713f9b453cf6827e573a4d38 172.38.0.16:6379@16379 slave 0a901487d8ff15de9e41706fbd5bb0bd99e9b052 0 1648628530031 6 connected
0a901487d8ff15de9e41706fbd5bb0bd99e9b052 172.38.0.12:6379@16379 master - 0 1648628530532 2 connected 5461-10922
b4607c2b19afd41f034ab49dac4dd7826d32cd34 172.38.0.15:6379@16379 slave 4c5124a0384f5dcc16fc2c46d4d898a00eac2af1 0 1648628530532 5 connected
cf80266d6a94f9f200ad0e75824a36e7d00ab1f0 172.38.0.13:6379@16379 master,fail - 1648628462156 1648628461847 3 connected
1、构建SpringBoot项目
2、打包运行
3、编写Dockerfile
#版本java8
FROM java:8
#把所有的jar复制到app.jar下
COPY *.jar /app.jar
#端口
CMD ["--server.port=8080"]
#暴露8080端口
EXPOSE 8080
#执行脚本命令 java -jar app.jar
ENTRYPOINT ["java","-jar","app.jar"]
4、上传jar和Dockerfile文件到服务器上
5、构建镜像
[root@wangjingkai idea]# docber build -t wangjingkai666 .
6、发布运行
docker run -d -P --name helloDockerfile wangjingkai666
[root@wangjingkai idea]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
33aecd9f2feb wangjingkai666 "java -jar app.jar -…" 21 seconds ago Up 20 seconds 0.0.0.0:49156->8080/tcp helloDockerfile
[root@wangjingkai idea]# curl localhost:49156/hello
hello,Dockerfile
Compose 是一个用于定义和运行多容器 Docker 应用程序的工具。
您可以使用 YAML 文件来配置应用程序的服务
使用一个命令,您可以从您的配置中创建并启动所有服务
Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration. To learn more about all the features of Compose, see the list of features.
Compose 适用于所有环境:生产、登台、开发、测试以及 CI 工作流程。
Compose works in all environments: production, staging, development, testing, as well as CI workflows. You can learn more about each case in Common Use Cases.
使用 Compose 基本上是一个三步过程:
Using Compose is basically a three-step process:
Dockerfile
so it can be reproduced anywhere.
Dockerfile
,以便可以在任何地方复制它。docker-compose.yml
so they can be run together in an isolated environment.
docker-compose.yml
以便它们可以在隔离环境中一起运行。docker compose up
and the Docker compose command starts and runs your entire app. You can alternatively run docker-compose up
using the docker-compose binary.
docker compose up
,Docker compose 命令启动并运行您的整个应用程序。您也可以docker-compose up
使用 docker-compose 二进制文件运行。狂神的理解
Compose 是Docker官方的开源项目。不在Docker内。需要安装。
Dockerfile
让程序在任何地方运行。 web服务,redis服务、。。。多个容器
一个docker-compose.yml
看起来像这样:
nkversion: "3.9" # optional since v1.27.0
services:
web:
build: .
ports:
- "8000:5000"
volumes:
- .:/code
- logvolume01:/var/log
links:
- redis
redis:
image: redis
volumes:
logvolume01: {}
Compose: 重要的概念
ca
我的理解:
Compose是单机多容器的一键部署解决方案。
1、运行以下命令下载 Docker Compose 的当前稳定版本:
对着官网来。https://docs.docker.com/compose/install/
此时2022-3-30 版本1.29.2
curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
#国内 速度快一些
curl -L https://get.daocloud.io/docker/compose/releases/download/1.25.5/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
出现报错:
curl: (1) Protocol “https” not supported or disabled in libcurl
错误原因:curl是利用URL语法在命令行方式下工作的开源文件传输工具。默认安装不支持https协议。
因为https协议是加密安全的基于http的协议,需要使用openssl的静态库,所以需要支持https就必须下载openssl。
1、如果已有openssl 找到curl目录下
2、./configure 查看最后的
3、如果为no 则输入下列命令
./configure --prefix=/usr/local/curl --with-ssl
#重新编译
make
#安装
make install
curl 就可以支持https了。
2、授权:对二进制文件应用可执行权限:
sudo chmod +x /usr/local/bin/docker-compose
3、测试安装
docker-compose --version
官网入门用例:https://docs.docker.com/compose/gettingstarted/
先决条件
预计阅读时间:11分钟
在此页面上,您将构建一个在 Docker Compose 上运行的简单 Python Web 应用程序。该应用程序使用 Flask 框架并在 Redis 中维护一个命中计数器。虽然示例使用 Python,但即使您不熟悉此处演示的概念,也应该可以理解。
确保您已经安装了Docker Engine 和Docker Compose。您不需要安装 Python 或 Redis,因为它们都是由 Docker 映像提供的。
定义应用程序依赖项。
为项目创建一个目录:
$ mkdir composetest
$ cd composetest
在项目目录中创建一个名为的文件app.py
并将其粘贴到:
vim app.py
import time
import redis
from flask import Flask
app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)
def get_hit_count():
retries = 5
while True:
try:
return cache.incr('hits')
except redis.exceptions.ConnectionError as exc:
if retries == 0:
raise exc
retries -= 1
time.sleep(0.5)
@app.route('/')
def hello():
count = get_hit_count()
return 'Hello World! I have been seen {} times.\n'.format(count)
在此示例中,redis
是应用程序网络上的 redis 容器的主机名。我们使用 Redis 的默认端口,6379
.
处理瞬态错误
注意
get_hit_count
函数的编写方式。如果 redis 服务不可用,这个基本的重试循环让我们可以多次尝试我们的请求。这在应用程序上线时启动时很有用,但如果在应用程序的生命周期内需要随时重新启动 Redis 服务,这也会使我们的应用程序更具弹性。在集群中,这也有助于处理节点之间的瞬时连接中断。
在您的项目目录中创建另一个名为的文件requirements.txt
并将其粘贴到:
vim `requirements.txt`
flask
redis
在此步骤中,您将编写一个构建 Docker 映像的 Dockerfile。该映像包含 Python 应用程序所需的所有依赖项,包括 Python 本身。
在您的项目目录中,创建一个名为Dockerfile
并粘贴以下内容的文件:
FROM python:3.7-alpine
WORKDIR /code
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
EXPOSE 5000
COPY . .
CMD ["flask", "run"]
这告诉 Docker:
/code
.flask
。requirements.txt
并安装 Python 依赖项。.
到镜像中的workdir .
。flask run
.有关如何编写 Dockerfile 的更多信息,请参阅 Docker 用户指南 和Dockerfile 参考。
在您的项目目录中创建一个名为的文件docker-compose.yml
并粘贴以下内容:
version: "3.9"
services:
web:
build: .
ports:
- "8000:5000"
redis:
image: "redis:alpine"
这个 Compose 文件定义了两个服务:web
和redis
.
网络服务
该服务使用从当前目录中web
构建的图像。Dockerfile
然后它将容器和主机绑定到暴露的端口,8000
. 此示例服务使用 Flask Web 服务器的默认端口,5000
.
Redis 服务
该redis
服务使用 从 Docker Hub 注册表中提取的公共Redis映像。
在您的项目目录中,通过运行docker-compose up
.
$ docker-compose up
Creating network "composetest_default" with the default driver
Creating composetest_web_1 ...
Creating composetest_redis_1 ...
Creating composetest_web_1
Creating composetest_redis_1 ... done
Attaching to composetest_web_1, composetest_redis_1
web_1 | * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
redis_1 | 1:C 17 Aug 22:11:10.480 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
redis_1 | 1:C 17 Aug 22:11:10.480 # Redis version=4.0.1, bits=64, commit=00000000, modified=0, pid=1, just started
redis_1 | 1:C 17 Aug 22:11:10.480 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
web_1 | * Restarting with stat
redis_1 | 1:M 17 Aug 22:11:10.483 * Running mode=standalone, port=6379.
redis_1 | 1:M 17 Aug 22:11:10.483 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
web_1 | * Debugger is active!
redis_1 | 1:M 17 Aug 22:11:10.483 # Server initialized
redis_1 | 1:M 17 Aug 22:11:10.483 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
web_1 | * Debugger PIN: 330-787-903
redis_1 | 1:M 17 Aug 22:11:10.483 * Ready to accept connections
Compose 拉取 Redis 映像,为您的代码构建映像,并启动您定义的服务。在这种情况下,代码在构建时被静态复制到映像中。
在浏览器中输入 http://localhost:8000/ 以查看正在运行的应用程序。
如果您在 Linux、Docker Desktop for Mac 或 Docker Desktop for Windows 上本地使用 Docker,那么 Web 应用程序现在应该在您的 Docker 守护程序主机上的端口 8000 上进行侦听。将您的 Web 浏览器指向 http://localhost:8000 以查找Hello World
消息。如果这不能解决,您也可以尝试 http://127.0.0.1:8000。
您应该会在浏览器中看到一条消息:
Hello World! I have been seen 1 times.
刷新页面。
数字应该增加。
Hello World! I have been seen 2 times.
切换到另一个终端窗口,然后键入docker image ls
以列出本地图像。
此时列出图像应返回redis
和web
。
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
composetest_web latest e2c21aa48cc1 4 minutes ago 93.8MB
python 3.4-alpine 84e6077c7ab6 7 days ago 82.5MB
redis alpine 9d8fa9aa0e5b 3 weeks ago 27.5MB
您可以使用docker inspect
.
停止应用程序,方法是docker-compose down
在第二个终端的项目目录中运行,或者在启动应用程序的原始终端中按 CTRL+C。
在您的项目目录中编辑docker-compose.yml
以添加服务的 绑定挂载web
:
version: "3.9"
services:
web:
build: .
ports:
- "8000:5000"
volumes:
- .:/code
environment:
FLASK_ENV: development
redis:
image: "redis:alpine"
新volumes
密钥将主机上的项目目录(当前目录)挂载到/code
容器内,允许您即时修改代码,而无需重建映像。environment
键设置 FLASK_ENV
环境变量,它告诉flask run
在开发模式下运行并在更改时重新加载代码。这种模式应该只在开发中使用。
在您的项目目录中,键入docker-compose up
以使用更新的 Compose 文件构建应用程序,然后运行它。
$ docker-compose up
Creating network "composetest_default" with the default driver
Creating composetest_web_1 ...
Creating composetest_redis_1 ...
Creating composetest_web_1
Creating composetest_redis_1 ... done
Attaching to composetest_web_1, composetest_redis_1
web_1 | * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
...
Hello World
再次在 Web 浏览器中检查消息,然后刷新以查看计数增量。
共享文件夹、卷和绑定装载
- 如果您的项目在
Users
目录 (cd ~
) 之外,那么您需要共享您正在使用的 Dockerfile 和卷的驱动器或位置。如果您收到运行时错误,指示未找到应用程序文件、卷安装被拒绝或服务无法启动,请尝试启用文件或驱动器共享。C:\Users
对于位于(Windows) 或/Users
(Mac)之外的项目,卷挂载需要共享驱动器 ,并且对于使用Linux 容器的 Docker Desktop for Windows 上的任何项目都是必需的。有关更多信息,请参阅Docker for Mac 上的文件共享,以及有关如何 管理容器中的数据的一般示例。- 如果您在较旧的 Windows 操作系统上使用 Oracle VirtualBox,您可能会遇到此VB 故障单中所述的共享文件夹问题。较新的 Windows 系统满足Docker Desktop for Windows的要求,不需要 VirtualBox。
由于应用程序代码现在使用卷安装到容器中,因此您可以对其代码进行更改并立即查看更改,而无需重建映像。
更改问候语app.py
并保存。例如,将Hello World!
消息更改为Hello from Docker!
:
return 'Hello from Docker! I have been seen {} times.\n'.format(count)
在浏览器中刷新应用程序。应更新问候语,并且计数器仍应递增。
如果您想在后台运行您的服务,您可以将-d
标志(用于“分离”模式)传递给docker-compose up
并用于docker-compose ps
查看当前正在运行的内容:
$ docker-compose up -d
Starting composetest_redis_1...
Starting composetest_web_1...
$ docker-compose ps
Name Command State Ports
-------------------------------------------------------------------------------------
composetest_redis_1 docker-entrypoint.sh redis ... Up 6379/tcp
composetest_web_1 flask run Up 0.0.0.0:8000->5000/tcp
该docker-compose run
命令允许您为您的服务运行一次性命令。例如,要查看 web
服务可以使用哪些环境变量:
$ docker-compose run web env
请参阅docker-compose --help
查看其他可用命令。您还可以为 bash 和 zsh shell 安装命令完成,它还会显示可用的命令。
如果您使用 Compose 开始docker-compose up -d
,请在完成后停止您的服务:
$ docker-compose stop
您可以使用命令将所有内容都关闭,完全删除容器down
。传递--volumes
也删除 Redis 容器使用的数据卷:
$ docker-compose down --volumes
至此,您已经了解了 Compose 的基本工作原理。
网络规则
多个服务 = 项目(项目中的内容都在同个网络下,可域名访问)
如果在同一个网络下,可以直接通过域名访问
小细节:依赖文件 requirements.txt
Docker镜像 run->容器
DockerFile 构建镜像(服务打包)
docker-compose 启动项目(编排、打包、运行多个服务、环境)
Docker 网络
官网介绍
# 总共三层
version: '' # 版本
services: #服务
服务1: web
# 服务配置
images:
port:
network:
depends_on: # 启动依赖(顺序)
...
服务2: redis
...
# 其他配置 网络、卷、全局规则
volumes:
network:
官网
预计阅读时间:3分钟
您可以使用 Docker Compose 在使用 Docker 容器构建的隔离环境中轻松运行 WordPress。本快速入门指南演示了如何使用 Compose 设置和运行 WordPress。在开始之前,请确保您已 安装 Compose。
创建一个空的项目目录。
您可以将目录命名为易于记忆的名称。此目录是您的应用程序映像的上下文。该目录应该只包含构建该图像的资源。
这个项目目录包含一个docker-compose.yml
完整的文件,它本身就是一个好的入门 wordpress 项目。
提示:您可以为此文件使用 a
.yml
或扩展名。.yaml
他们都工作。
切换到您的项目目录。
例如,如果您将目录命名为my_wordpress
:
$ cd my_wordpress/
创建一个docker-compose.yml
用于启动您的 WordPress
博客的文件和一个带有卷挂载的单独MySQL
实例以实现数据持久性:
version: "3.9"
services:
db:
image: mysql:5.7
volumes:
- db_data:/var/lib/mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: somewordpress
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
wordpress:
depends_on:
- db
image: wordpress:latest
volumes:
- wordpress_data:/var/www/html
ports:
- "8000:80"
restart: always
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpress
volumes:
db_data: {}
wordpress_data: {}
备注:
- docker 卷
db_data
并wordpress_data
持久化 WordPress 对数据库的更新,以及已安装的主题和插件。了解有关 docker 卷的更多信息- WordPress Multisite 仅适用于端口
80
和443
.
现在,docker-compose up -d
从您的项目目录运行。
这docker-compose up
在分离模式下运行,拉取所需的 Docker 映像,并启动 wordpress 和数据库容器,如下例所示。
$ docker-compose up -d
Creating network "my_wordpress_default" with the default driver
Pulling db (mysql:5.7)...
5.7: Pulling from library/mysql
efd26ecc9548: Pull complete
a3ed95caeb02: Pull complete
<...>
Digest: sha256:34a0aca88e85f2efa5edff1cea77cf5d3147ad93545dbec99cfe705b03c520de
Status: Downloaded newer image for mysql:5.7
Pulling wordpress (wordpress:latest)...
latest: Pulling from library/wordpress
efd26ecc9548: Already exists
a3ed95caeb02: Pull complete
589a9d9a7c64: Pull complete
<...>
Digest: sha256:ed28506ae44d5def89075fd5c01456610cd6c64006addfe5210b8c675881aff6
Status: Downloaded newer image for wordpress:latest
Creating my_wordpress_db_1
Creating my_wordpress_wordpress_1
注意:WordPress Multisite 仅适用于端口
80
和/或443
.0.0.0.0
如果您收到有关绑定到端口80
或443
(取决于您指定的端口)的错误消息,则您为 WordPress 配置的端口可能已被其他服务使用。
至此,WordPress 应该已经在8000
你的 Docker Host 的端口上运行了,你可以以 WordPress 管理员的身份完成“著名的五分钟安装”。
注意: WordPress 站点不能立即在端口上可用,
8000
因为容器仍在初始化中,可能需要几分钟才能首次加载。
如果您使用的是 Docker Desktop for Mac 或 Docker Desktop for Windows,您可以将 http://localhost
其用作 IP 地址,并http://localhost:8000
在 Web 浏览器中打开。
该命令docker-compose down
会删除容器和默认网络,但会保留您的 WordPress 数据库。
该命令docker-compose down --volumes
删除容器、默认网络和 WordPress 数据库。
编写微服务
Dockerfile 构建镜像
FROM java:8
COPY *.jar /app.jar
CMD ["--server.port=8080"]
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"]
docker-compose.yml 编排项目
version: '3.3'
services:
app:
build: .
image: app
depends_on:
- redis
ports:
- "8080:8080"
redis:
image: "redis:alpine"
docker-compose up 启动运行
集群方式的部署、
使用VM 创建四个虚拟机。(别问为什么没用云服务器, 问就是穷)
需要的问题解决方案放在博客里。
https://editor.csdn.net/md/?articleId=123874053
预计阅读时间:2分钟
Docker Engine 1.12 引入了 swarm 模式,使您能够创建一个由一个或多个 Docker 引擎组成的集群,称为 swarm。一个 swarm 由一个或多个节点组成:在 swarm 模式下运行 Docker Engine 1.12 或更高版本的物理机或虚拟机。
有两种类型的节点:管理器和 工作器。
如果您还没有,请通读 swarm 模式概述和 关键概念。
Manager 节点处理集群管理任务:
使用Raft实现,管理器维护整个 swarm 和在其上运行的所有服务的一致内部状态。出于测试目的,可以使用单个管理器运行 swarm。如果单管理器集群中的管理器发生故障,您的服务将继续运行,但您需要创建一个新集群才能恢复。
为了利用 swarm 模式的容错特性,Docker 建议您根据组织的高可用性要求实现奇数个节点。当您有多个管理器时,您可以从管理器节点的故障中恢复而无需停机。
一个三管理器群最多可以容忍一名管理器的损失。
一个五管理器群最多可以同时丢失两个管理器节点。
一个N
管理器集群最多可以容忍丢失 (N-1)/2
管理器。
Docker 建议一个 swarm 最多使用七个管理器节点。
重要提示:添加更多管理器并不意味着增加可扩展性或提高性能。一般来说,情况正好相反。
工作节点也是 Docker 引擎的实例,其唯一目的是执行容器。Worker 节点不参与 Raft 分布式状态,不做调度决策,也不服务于 swarm 模式的 HTTP API。
您可以创建一个由一个管理器节点组成的集群,但如果没有至少一个管理器节点,您就不能拥有一个工作程序节点。默认情况下,所有Manager 也是工人。在单个管理节点集群中,您可以运行类似docker service create
的命令,并且调度程序将所有任务放在本地引擎上。
要防止调度程序将任务放置在多节点集群中的管理器节点上,请将管理器节点的可用性设置为Drain
。调度器优雅地停止Drain
模式节点上的任务,并在一个 Active
节点上调度任务。调度程序不会将新任务分配给Drain
可用的节点。
请参阅docker node update
命令行参考以了解如何更改节点可用性。
您可以通过运行将工作节点提升为管理器docker node promote
。例如,当您将管理节点脱机进行维护时,您可能希望提升工作节点。请参阅节点提升。
manager:
worker node:
只能在节点运行,提供相同的内容
For a replicated service, you specify the number of identical tasks you want to run. For example, you decide to deploy an HTTP service with three replicas, each serving the same content.
可在任意位置运行
A global service is a service that runs one task on every node. There is no pre-specified number of tasks. Each time you add a node to the swarm, the orchestrator creates a task and the scheduler assigns the task to the new node. Good candidates for global services are monitoring agents, an anti-virus scanners or other types of containers that you want to run on every node in the swarm.
--mode
docker service create --mode replicated 默认的 --mode global
场景:
日志收集:每一个节点有自己的日志收集器,过滤,再把所有日志最终传给日志中心。
服务监控
# 第一台机器 初始化为manager节点
[root@localhost ~]# docker swarm init --advertise-addr 192.168.230.128
Swarm initialized: current node (ury2qyztaj54axkg67q3kqesy) is now a manager.
To add a worker to this swarm, run the following command:
#执行这段命令是作为worker加入
docker swarm join --token SWMTKN-1-0zc1dw53ijhztlw8ke0s9tq04l1pgyck5mmqh5khnulanwq60d-efkiq3jdmjf5koxa9thr43gzx 192.168.230.128:2377
# 要作为作为manger加入的话执行docker swarm join-token manager获取令牌
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
# 第二台、第三台机器加入,作为一个worker加入
[root@localhost ~]# docker swarm join --token SWMTKN-1-0zc1dw53ijhztlw8ke0s9tq04l1pgyck5mmqh5khnulanwq60d-efkiq3jdmjf5koxa9thr43gzx 192.168.230.128:2377
This node joined a swarm as a worker.
# 第四台机器作为 manager 加入
# 先在第一台机器执行 docker swarm join-token manager 同理也可得到worker
# 将得到的token复制到第四台机器
[root@localhost ~]# docker swarm join --token SWMTKN-1-0zc1dw53ijhztlw8ke0s9tq04l1pgyck5mmqh5khnulanwq60d-c8lrxfz4tq0zdrqyj2hgh553d 192.168.230.128:2377
This node joined a swarm as a manager.
# 这里可能会报错:Error response from daemon: rpc error: code = Unavailable desc = all SubConns are in TransientFailure, latest connection error: connection error: desc = "transport: Error while dialing dial tcp 192.168.0.108:2377: connect: no route to host"这个错误是因为将node节点加入swarm中导致的,原因就是manager节点这台机器上的防火墙没有关闭。只要把manager这台机器上的防火墙关闭即可:
#这里因为是我VM 所以直接关了,阿里云可以直接开放端口2377即可。
#停止防火墙
[root@centos-7 ~]# systemctl stop firewalld.service
#永久关闭防火墙
[root@centos-7 ~]# systemctl disable firewalld.service
# 也可以使用命令 使 worker 升级为 manager节点
docker node promote
# 查看集群信息
[root@localhost ~]# docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
1yhk7xykb008hgxlwykqd7ao2 localhost.localdomain Ready Active 20.10.14
ryqtqead5yfaoiiade1xzw6te localhost.localdomain Ready Active 20.10.14
tu7vum1aj24lgsz71886ia5bh * localhost.localdomain Ready Active Reachable 20.10.14
ury2qyztaj54axkg67q3kqesy localhost.localdomain Ready Active Leader 20.10.14
#扩展:worker节点 使用 docker swarm leave 就可离开。 Manager 需要强制离开 docker swarm leave -f
#docker run 容器启动、不具备扩缩容器
#docker service 集群启动、具备扩缩能力 使用它的前提是已经搭建好集群了
[root@localhost ~]# docker service --help
Usage: docker service COMMAND
Manage services
Commands:
create Create a new service #创建
inspect Display detailed information on one or more services #详情
logs Fetch the logs of a service or task #日志
ls List services
ps List the tasks of one or more services
rm Remove one or more services
rollback Revert changes to a service's configuration
scale Scale one or multiple replicated services #扩缩容
update Update a service #更新
# 启动 nginx
[root@localhost ~]# docker service create -p 8888:80 --name my-nginx nginx
j5yu04xqlfe1wel9pl66pigeg
overall progress: 1 out of 1 tasks
1/1: running
verify: Service converged
# 查看my-nginx 服务的状态 非 Leader 节点上是不能查看的
[root@localhost ~]# docker service ps my-ngnix
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
zyo6ytclwh1z my-nginx.1 nginx:latest localhost.localdomain Running Running about a minute ago
#查看服务
[root@localhost ~]# docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
j5yu04xqlfe1 my-nginx replicated 1/1 nginx:latest *:8888->80/tcp
# 扩容到3个副本 集群中的任意节点都可访问,实现高可用 == docker service scale my-nginx=3
# 也可以使用 docker service update --replicas 3 my-nginx -d 后台启动
[root@localhost ~]# docker service update --replicas 3 my-nginx
my-ngnix
overall progress: 1 out of 3 tasks
1/3: running
2/3: preparing
3/3: preparing
集群的管理和编排。docker可初始化一个 swarm 集群,其他节点可加入(manager、worker)
就是一个docker节点。多个节点组成了一个网络集群(manager、worker)
服务、可管理节点或者工作节点运行
容器内的命令
官网
网络模式:PublishMode:ingress
Overlay:加一层,由中间这层去统一调度到任意机器
ingress:特殊的 Overlay 网络,具有负载均衡功能 IPVS VIP
[root@localhost ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
34a48b87b6cc bridge bridge local
836c7b6605fd docker_gwbridge bridge local
88167e9c740f host host local
q0lno7d93ndl ingress overlay swarm
6162453aafff none null local
[root@localhost ~]# docker network inspect q0lno7d93ndl
# 初始化为manager节点
docker swarm init --advertise-addr ip地址
# 作为manager加入(在manager执行)
docker swarm join-token manager #将得到的命令复制到要加入的节点中
# 作为worker 加入(在manager执行)
docker swarm join-token worker #将得到的命令复制到要加入的节点中
# worker 升级为 manager节点
docker node promote
# 查看集群信息 leader为manager节点,Reachable 为后备manager节点(挂掉之后会选举出新的)
docker node ls
# 离开集群
docker swarm leave
# 启动服务 eg:nginx
# 之前 docker 的参数都可用
docker service create -p 8000:80 --name my-nginx nginx
# 查看服务状态
docker service ps 服务名称
# 查看服务列表(只能在manager执行)
docker service ls
# 扩缩容,两种方式
docker service update --replicas 数量 名字|服务id
docker service scale 名字|服务id=数量
Docker Compose,缺点是不能在分布式多机器上使用;我们还介绍了Docker swarm,缺点是不能同时编排多个服务,所以才有了Docker Stack,可以在分布式多机器上同时编排多个服务。
案例
https://www.jianshu.com/p/d250b91c7adf
一个stack 是一组相互关联的 service,这组 service共享依赖,可被安排在一起运行和扩展
# docker-compose 单机部署
docker-compose up -d
# docker stack 集群部署
# docker stack deploy -c [docker-compose.yml] [service name]
docker stack deploy -c docker-compose.yml mygoweb
docker stack deploy myapps --compose-file=/wordpress.yaml
# 查看所有stack
docker stack ls
官网描述:Manage Docker secrets 管理 Docker 机密
我们知道有的service是需要设置密码的,比如mysql服务是需要设置密码的:
比如:docker-compose.yml中的service密码都是明文,这样就导致了不是很安全。
我们知道manager节点保持状态的一致是通过Raft Database这个分布式存储的数据库,它本身就是将信息进行了secret,所以可以利用这个数据库将一些敏感信息,例如账号、密码等信息保存在这里,然后通过给service授权的方式允许它进行访问,这样达到避免密码明文显示的效果。
总之,secret的Swarm中secret的管理通过以下步骤完成:
[root@localhost ~]# docker secret --help
Usage: docker secret COMMAND
Manage Docker secrets
Commands:
create Create a secret from a file or STDIN as content
inspect Display detailed information on one or more secrets
ls List secrets
rm Remove one or more secrets
[root@localhost ~]# docker secret create --help
Usage: docker secret create [OPTIONS] SECRET [file|-]
Create a secret from a file or STDIN as content
#可以看到说明secret可以来自于一个文件或者一个标准输出。那么也就是Secret的创建有两种方式,分别是:
#基于命令行创建
#基于文件的创建
Options:
-d, --driver string Secret driver
-l, --label list Secret labels
--template-driver string Template driver
#首先先创建一个文件用于存放mysql密码 密码为:root
[root@localhost home]# vim mysql-password
[root@localhost home]# cat mysql-password
root
#创建secret 基于文件创建
#这些都是建立在有一个集群的基础上的。要搭建好docker swarm.
#mysql-pass是secret的名称,mysql-password是我们建立存储密码的文件,这样执行后就相当于将文件中的密码存储在Swarm中manager节点的Raft Database中了
[root@localhost home]# docker secret create mysql-pass mysql-password
uwrfxavvhpm3opyx7hyvdxkhk
#为了安全起见,现在可以直接将这个文件删掉,因为Swarm中已经有这个密码了。
[root@localhost home]# rm -f mysql-password
#查看secret列表
[root@localhost home]# docker secret ls
ID NAME DRIVER CREATED UPDATED
uwrfxavvhpm3opyx7hyvdxkhk mysql-pass About a minute ago About a minute ago
#创建secret 基于命令创建
#docker secret create [OPTIONS] CONFIG file|-
[root@localhost home]# echo "root" | docker secret create mysql-pass2 -
#查看secret列表
[root@localhost home]# docker secret ls
ID NAME DRIVER CREATED UPDATED
uwrfxavvhpm3opyx7hyvdxkhk mysql-pass 2 minutes ago 2 minutes ago
9mis56r456xj55tkvnn3npq42 mysql-pass2 7 seconds ago 7 seconds ago
#查看更多信息
# docker secret inspect mysql-pass2
[
{
"ID": "9mis56r456xj55tkvnn3npq42",
"Version": {
"Index": 101
},
"CreatedAt": "2022-04-01T02:42:51.986190083Z",
"UpdatedAt": "2022-04-01T02:42:51.986190083Z",
"Spec": {
"Name": "mysql-pass2",
"Labels": {}
}
}
]
#删除secret
[root@localhost home]# docker secret rm mysql-pass2
mysql-pass2
[root@localhost home]# docker secret ls
ID NAME DRIVER CREATED UPDATED
uwrfxavvhpm3opyx7hyvdxkhk mysql-pass 4 minutes ago 4 minutes ago
#Secret在单容器中的使用
#容器中查看secret,启动一个服务后,将其授权给特定的服务使其可以看到
#docker service create -secret mysql-pass(密码名字) 创建服务时可以给服务暴露出secret
#创建服务
[root@localhost home]# docker service create --name demo --secret mysql-pass busybox sh -c "while true; do sleep 3600; done"
l5tc9hplb9kdue0ogu7tzazc2
overall progress: 1 out of 1 tasks
1/1: running
verify: Service converged
#查看这个服务运行在那个节点上
[root@localhost home]# docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
l5tc9hplb9kd demo replicated 1/1 busybox:latest
j5yu04xqlfe1[root@localhost home]# docker service ps demo
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
c1b8oz3ppbn6 demo.1 busybox:latest localhost.localdomain Running Running about a minute agomy-ngnix replicated 3/3 nginx:latest *:8888->80/tcp
#可以看到这个服务运行在localhost.localdomain主机的节点上,去这个节点上进入到容器内部,查看secret:
[root@localhost home]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
bd823abfcf67 busybox:latest "sh -c 'while true; …" About a minute ago Up About a minute demo.1.c1b8oz3ppbn685shhvg83egik
[root@localhost home]# docker exec -it bd823abfcf67 /bin/sh
/ # cd /run/secrets/
/run/secrets # ls
mysql-pass
/run/secrets # cat mysql-pass
root
#mysql服务
#关于mysql镜像官网有关于secret的描述:
#作为通过环境变量传递敏感信息的替代方法,_FILE可以将其附加到先前列出的环境变量中,从而使初始化脚本从容器中存在的文件中加载那些变量的值。
#特别是,这可用于从/run/secrets/文件中存储的Docker Secret加载密码。例如:
#$ docker run --name some-mysql -e MYSQL_ROOT_PASSWORD_FILE=/run/secrets/mysql-root -d mysql:tag
#目前,这仅支持MYSQL_ROOT_PASSWORD,MYSQL_ROOT_HOST,MYSQL_DATABASE,MYSQL_USER,和MYSQL_PASSWORD。
#所以我们需要先创建一个文件secret用于存储数据库的敏感信息,因为之前已经创建过,这里无需再创建:
#再次查看secret 命令多打打才不容易忘记
[root@localhost home]# docker secret ls
ID NAME DRIVER CREATED UPDATED
uwrfxavvhpm3opyx7hyvdxkhk mysql-pass 4 minutes ago 4 minutes ago
#启动mysql服务:
[root@localhost home]# docker service create --name db-mysql --secret mysql-pass -e MYSQL_ROOT_PASSWORD_FILE=/run/secrets/mysql-pass mysql:5.7
w1ptd75g4locdme9zct7opiy2
overall progress: 0 out of 1 tasks
1/1: preparing
^COperation continuing in background.
Use `docker service ps w1ptd75g4locdme9zct7opiy2` to check progress.
[root@localhost home]# docker service ps w1ptd75g4locdme9zct7opiy2
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
fv4arc2gq6y8 db-mysql.1 mysql:5.7 localhost.localdomain Running Preparing 3 minutes ago
#查看mysql服务在那个节点上:
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
55feeb983b81 mysql:5.7 "docker-entrypoint.s…" 2 minutes ago Up 2 minutes 3306/tcp, 33060/tcp db-mysql.1.fv4arc2gq6y8g7lr1m4c7sefn
#在该节点中进入该服务的容器中查看secret:
[root@localhost ~]# docker exec -it 55feeb983b81 /bin/sh
# cd /run/secrets/
# ls
mysql-pass
# cat mysql-pass
root
#这样知道了密码就可以进入到mysql数据库中了。
# mysql -uroot -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.36 MySQL Community Server (GPL)
Copyright (c) 2000, 2021, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
Stack利用的就是docker-compose.yml文件来部署stack,那么如何在docker-compose.yml中来定义secret呢?
mysql:
image: mysql
secrets:
- my-pw
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mysql-pass #制定secret
MYSQL_DATABASE: wordpress
显然我们在运行这个docker-compose.yml文件之前必须先要进行对应的secret文件的创建。然后就可以通过docker stack deploy命令来部署这个stack了。
部分参考 https://www.cnblogs.com/shenjianping/p/12272847.html 感谢博主分享。
在集群环境中配置文件的分发,可以通过将配置文件放入镜像中、设置环境变量、挂载volume、挂载目录的方式,当然也可以通过 docker config 来管理集群中的配置文件,这样的方式也更加通用。
#一切命令从help学起
[root@localhost home]# docker config --help
Usage: docker config COMMAND
Manage Docker configs
Commands:
create Create a config from a file or STDIN
inspect Display detailed information on one or more configs
ls List configs
rm Remove one or more configs
Run 'docker config COMMAND --help' for more information on a command.
[root@localhost home]# docker config create --help
Usage: docker config create [OPTIONS] CONFIG file|-
Create a config from a file or STDIN(stdin 标准输入)
#可以看出创建可以分为 文件创建,命令创建
Options:
-l, --label list Config labels
--template-driver string Template driver
#文件创建
[root@localhost home]# vim default.conf
[root@localhost home]# cat default.conf
server {
listen 88;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
#创建config
[root@localhost home]# docker config create conf default.conf
ouyt1mlylntrzyd4qw74f5qx6
#查看config
[root@localhost home]# docker config ls
ID NAME CREATED UPDATED
ouyt1mlylntrzyd4qw74f5qx6 conf 19 seconds ago 19 seconds ago
#从标准输入创建
#Usage: docker config create [OPTIONS] CONFIG file|-
[root@localhost home]# echo "listen 80" | docker config create conf2 -
[root@localhost home]# docker config ls
ID NAME CREATED UPDATED
ouyt1mlylntrzyd4qw74f5qx6 conf 7 minutes ago 7 minutes ago
7eqao1yfzpjqfk4zhh35o7osj conf2 34 seconds ago 34 seconds ago
#查看config详细信息
[root@localhost home]# docker config inspect conf
[
{
"ID": "ouyt1mlylntrzyd4qw74f5qx6",
"Version": {
"Index": 117
},
"CreatedAt": "2022-04-01T03:29:25.097432457Z",
"UpdatedAt": "2022-04-01T03:29:25.097432457Z",
"Spec": {
"Name": "conf",
"Labels": {},
"Data": "c2VydmVyIHsKICAgIGxpc3RlbiAgICAgICA4ODsKICAgIHNlcnZlcl9uYW1lICBsb2NhbGhvc3Q7CgogICAgbG9jYXRpb24gLyB7CiAgICAgICAgcm9vdCAgIC91c3Ivc2hhcmUvbmdpbngvaHRtbDsKICAgICAgICBpbmRleCAgaW5kZXguaHRtbCBpbmRleC5odG07CiAgICB9Cn0K"
}
}
]
#对conf进行base64解码
[root@localhost home]# docker config inspect -f '{{json .Spec.Data}}' conf | cut -d '"' -f2 | base64 -d
#出现结果
server {
listen 88;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
#删除secret
[root@localhost home]# docker config rm conf2
#docker config
#使用nginx镜像创建容器
#在conf配置中,将nginx的监听端口改成了88 --config source=conf,
#然后再替换掉nginx中的默认80端口的配置文件 target=/etc/nginx/conf.d/default.conf,
#创建service时,将容器内部端口88端口映射成主机上90端口,-p 90:88
#后台运行。-d
[root@localhost ~]# docker service create --name nginx-01 --config source=conf,target=/etc/nginx/conf.d/default.conf -p 90:88 nginx:latest
yuu4hnqk2masmlr11v4tya83y
overall progress: 1 out of 1 tasks
1/1: running
verify: Service converged
#测试
[root@localhost ~]# curl http://127.0.0.1:90
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
CI/CD是实现敏捷和Devops理念的一种方法。
具体而言,CI/CD 可让持续自动化和持续监控贯穿于应用的整个生命周期(从集成和测试阶段,到交付和部署)。这些关联的事务通常被统称为“CI/CD 管道”,由开发和运维团队以敏捷方式协同支持。
定义: is the practice of merging all developers' working copies to a shared mainline
several times a day
持续集成指的是,频繁地(一天多次)将代码集成到主干。
CI是一种通过在应用开发阶段引入自动化来频繁向客户交付应用的方法。CI 的核心概念是持续集成、持续交付和持续部署。作为一个面向开发和运营团队的解决方案,CI 主要针对在集成新代码时所引发的问题(亦称“集成地狱”)。
持续集成强调开发人员提交了新代码之后,立刻自动的进行构建、(单元)测试。根据测试结果,我们可以确定新代码和原有代码能否正确地集成在一起。
持续集成的目的
就是让产品可以快速迭代,同时还能保持高质量**。**它的核心措施是,代码集成到主干之前,必须通过自动化测试。只要有一个测试用例
,就不能集成。
持续集成过程中很重视自动化测试验证结果,对可能出现的一些问题进行预警,以保障最终合并的代码没有问题。
持续集成的作用
特点
It aims at building, testing, and releasing software with greater speed and frequency
持续交付与DevOps的含义很相似,所以经常被混淆。但是它们是两个不同的概念。
DevOpsfa范围更广,它以文化变迁为中心,特别是软件交付过程所设计的多个团队之间的合作。一种思想。
CD持续交付,是一种自动化交付的手短,关注点在于将不同的过程集中起来,并且更快,更频繁的执行这些过程。
关于打赏:
打赏后如需要笔记,请主动发支付信息到邮箱 [email protected],备注需要的笔记名称。笔记持续更新。
ps:
另外本人创建了一个java交流群,可以在群里交流各自问题,看到了有能力的情况下,会尽力帮忙解决。
即为交流,希望是愿意交流的人才进来,一起努力、进步。
需要进群的 发送邮件 [email protected] 备注 进群即可。(无需打赏)
如果本文有帮到了你,可以帮忙点个赞,谢谢支持。