Docker精通:微服务
Docker进阶
学完Docker安装到入门这篇文章后,相信大家对Docker有了较多的理解。下面是一些高级的使用,适用于分布式搭建等
八、 Docker网络
1、 理解网络
1.1 问题引出
首先我们需要清空所有环境:
docker rm -f $(docker ps -aq) docker rmi -f $(docker images -aq)
查看本机的网络:
我们看到了三个不同的网卡
问题:docker是如何处理容器间的访问的?
-
首先我们进行一个测试,查看本机能否ping通容器内部:
docker run -d -P --name tomcat01 tomcat # 我们先启动一个容器 docker exec -it tomcat01 /bin/bash # 安装网络连接工具 apt update && apt install -y iproute2 apt-get install inetutils-ping # 安装ping工具 ip addr # 查看容器的ip地址 -
我们查看能不能互相ping通
ping 172.17.0.2 # 主机ping容器内部 ping 10.0.2.15 # 容器内部ping主机 我们发现容器和主机之间可以相互ping通
1.2 原理
- 我们每启动一个docker容器,docker就会给docker容器分配一个ip,我们只需要安装了docker,就会有一个网卡 docker0 桥接模式,使用的技术是 evth-pair 技术!
再次测试ip addr
在启动一个容器测试,发现又多了一对网卡,主机一个,容器中一个!
我们发现这个容器到来的网卡,是一对一对的:
-
evth-pair
:一对的虚拟设备接口,他们都是成对出现的,一段连着协议,一段彼此相连 -
正因为有这个特性,
evth-pair
充当各种虚拟网络设备- docker容器之间的连接
所有的容器不指定网络的情况下,都是docker0路由的,docker会给我们的容器分配一个默认的可用ip。
Docker使用的是Linux的桥接,宿主机是一个Docker容器的网桥 docker0
Docker中所有网络接口都是虚拟的,虚拟的转发效率高(内网传递文件)
只要容器删除,对应的网桥一对就没了!
2、 容器之间通信
2.1 --link
思考一个场景,我们编写一个微服务,database_url = ip,项目不重启,数据库ip换掉了,我们能不能使用名字来访问容器?
docker run --link
可以用来链接2个容器,使得源容器(被链接的容器)和接收容器(主动去链接的容器)之间可以互相通信,并且接收容器可以获取源容器的一些数据,如源容器的环境变量。
测试:
-
开启三个容器:
docker run -it -d --name centos01 centos /bin/bash docker run -it -d --name centos02 centos /bin/bash docker run -it --name centos03 --link centos02 centos /bin/bash -
测试能否通信
docker exec -it centos03 ping centos02 # 可以ping通 docker exec -it centos02 ping centos03 # 不可以ping通 docker exec -it centos01 ping centos02 # 不可以ping通
docker网络探究:
docker netword ls # NETWORK ID NAME DRIVER SCOPE # 297f07c51979 bridge bridge local # bridge:桥接 docker0 # 87a8f8309bfa host host local # 51b67872ea20 none null local
全部命令:
[root@192 ~]# docker network --help Usage: docker network COMMAND Manage networks # 管理网络 Commands: connect # 将容器连接到网络 create # 创建网络 disconnect # 断开一个容器与网络的连接 inspect # 显示一个或多个网络的详细信息 ls # 网络列表 prune # 删除所有未使用的网络 rm # 删除一个或多个网络
我们来查看一下容器与网络相关的信息:
docker network inspect 297f07c51979
我们首先来查看Tomcat02的信息
docker inspect 78904cf54659
啥也没发现
然后我们再来查看Tomcat03的信息
docker inspect 4e6aae2d99dd
# ... "HostConfig": { # ... "Links": [ "/tomcat02:/tomcat03/tomcat02" # 发现了tomcat02读得相关信息 ], # ... } # ... 说明Tomcat03与Tomcat02之间有ip映射的关系
本质就是:容器tomcat03配置了网络映射,将主机名(tomcat02)与ip地址host映射(不推荐使用)。
cat /etc/hosts
我们现在已经不建议使用--link
了
docker0的问题:他不支持容器名直接访问
2.2 自定义网络
查看所有的docker网络:
网络模式:
bridge
:桥接 docker(自己创建也是用桥接模式)none
:不配置网络,默认host
:和宿主机共享网络
运行docker
docker run -d -P --name tomcat01 --net bridge tomcat # 我们直接启动命令 --net bridge,这个就是默认启动命令,也是docker0 # docker0特点:默认,域名不能访问,--link可以打通连接 # 我们可以自定义网络
我们创建网络的命令
[root@myCentOS ~]# docker network create --help Usage: docker network create [OPTIONS] NETWORK Create a network Options: --attachable # 启用手动容器附件 --aux-address map # map网络驱动使用的辅助IPv4或IPv6地址(默认map[]) --config-from string # 要复制配置的网络 --config-only # 创建仅配置网络 -d, --driver string # 管理网络的驱动程序(默认为“bridge”) --gateway strings # IPv4或IPv6主子网网关 --ingress # 创建群路由-mesh网络 --internal # 限制外部用户访问网络 --ip-range strings # 从子范围分配容器ip --ipam-driver string # IP地址管理驱动程序(默认为“default”) --ipam-opt map # map设置IPAM驱动的特定选项(default map[]) --ipv6 # 启用ipv6组网功能 --label list # list设置网络元数据 -o, --opt map # 设置驱动程序的特定选项(默认map[]) --scope string # 控制网络范围 --subnet strings # CIDR格式的子网,表示一个网段
实例:
docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet
比如,我们创建一个网络,设置子网为:
192.168.0.0/16
,设置网关为:192.168.0.1
,驱动为桥接模式。家里面的路由器默认就是这个配置然后我们就发现我们创建的网络了。
查看我们创建的网络:
使用我们创建的网络:
docker run -d --name n1 --net mynet tomcatnet docker run -d --name n2 --net mynet tomcatnet
我们再来查看我们创建的自定义网络:发现增加了我们创建的两个容器
docker network inspect mynet
我们来看一下两个容器能否互相ping通
docker exec -it n1 ping n2 docker exec -it n2 ping n1
我们自定义的网络docker都已经帮我们维护好了对应的关系,推荐平时这样使用网络
好处:
- redis:不同的集群使用不同的网络,保证集群是安全的
2.3 网络连通
解决问题,网段不同如何ping通
需求,我们需要从docker01连接到mynet
# 我们发现 docker01到mynet无法连通的 (base) [root@MyCentOS test]# docker run --name no1 -d -P tomcatnet 9792d84e8abf48359b6d05304ecea472ffd25d32731452412856fc07167237b4 (base) [root@MyCentOS test]# docker exec -it no1 ping n1 ping: unknown host
解决方法:我们使用docker network connect
命令
[root@192 ~]# docker network connect --help # NETWORK:工作网络(mynet) Usage: docker network connect [OPTIONS] NETWORK CONTAINER Connect a container to a network # 将容器连接到网络中 Options: --alias strings # 为容器添加网络范围的别名 --driver-opt strings # 网络驱动选项 --ip string # IPv4地址(例如172.30.100.104) --ip6 string # IPv6地址(例如,2001:db8:: 33) --link list # 添加链接到另一个容器 --link-local-ip strings # 为容器添加链路本地地址
例如:
docker network connect mynet no1 # 把no1直接与mynet连通 # 相当于一个服务器,有两个ip,内网和公网
注意,只是一个容器与mynet连通了,不是docker01这个网段与mynet连通了
结论:假设要换网络连通别人,就需要使用docker network connect
连通
3、 实战
3.1 部署redis集群
首先,我们创建网卡:
docker network create redisnet --subnet 172.38.0.0/16
我们使用shell脚本创建六个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
通过脚本创建6个redis容器:
for port in $(seq 1 6);\ do \ docker run -p 637${port}:6379 -p 1667${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 redisnet --ip 172.38.0.1${port} redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf done
redis-server /etc/redis/redis.conf
: 以配置文件启动redis
我们查看我们创建的集群;
docker ps docker inspect redisnet # 查看网络信息
创建集群:
docker exec -it redis-1 /bin/sh # 进入redis中 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 # 然后输入yes就配置完毕
测试部署好的集群:
redis-cli -c # 进入集群模式 # 查看集群信息 127.0.0.1:6379> cluster nodes 35fcc7906fda3c977cf6fe09ebfd4632c8288449 172.38.0.15:6379@16379 slave 7866a044a62217acc264e0780724baebfcbdf9ec 0 1643970509988 5 connected a2d9d996b447f724860502d4230434d8fbef45bb 172.38.0.13:6379@16379 master - 0 1643970508952 3 connected 10923-16383 59c81a95ed20b8964246dee3bfc333e25960275a 172.38.0.16:6379@16379 slave e2210b65a712c43fce9cb11a40605d4ae6afc61c 0 1643970509257 6 connected e2210b65a712c43fce9cb11a40605d4ae6afc61c 172.38.0.12:6379@16379 master - 0 1643970508000 2 connected 5461-10922 eaa1f8ba8de966a6fffaed6b43e0226d95caffb9 172.38.0.14:6379@16379 slave a2d9d996b447f724860502d4230434d8fbef45bb 0 1643970509000 4 connected 7866a044a62217acc264e0780724baebfcbdf9ec 172.38.0.11:6379@16379 myself,master - 0 1643970507000 1 connected 0-5460
集群搭建完成
3.2 SpringBoot微服务部署
步骤:
- 构建SpringBoot微服务项目
- 打包应用
- 编写dockerfile
- 构建镜像
- 发布运行
构建SpringBoot项目
package com.example.demo.colltroller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloColltroer { @RequestMapping("/hello") public String hello(){ return "hello world"; } }
双击Maven Package进行打包
最后打包成功,测试是否可以运行
java -jar demo1-0.0.1-SNAPSHOT.jar
上传jar包:
在idea项目根目录创建并编写Dockerfile文件
FROM java:11 COPY *.jar /app.jar CMD ["--server.port=8080"] EXPOSE 8080 ENTRYPOINT ["java","-jar","app.jar"]
将jar包和Dockerfile上传到服务器中:
[root@192 idea]# ls demo1-0.0.1-SNAPSHOT.jar Dockerfile
构建镜像:
docker build -t springboot .
运行docker容器:
docker run -dit -p 8080:8080 --name hello springboot
测试能否访问成功:
curl 127.0.0.1:8080/hello
发布镜像:
docker login -u 【你的用户名】 -p 【你的密码】 # docker tag 镜像id 你的账户名/镜像仓库名:tag名 docker tag f107ab2a9246 dockerywl/springboot # docker push 作者/镜像:TAG(版本) docker push dockerywl/springboot
以后我们使用了Docker之后,给别人交付就是一个镜像即可!
九、 DockerCompose
1、 简介
1.1 微服务
微服务的基本特点:
- 对外暴露单一:对外只有一个HTTP接口
- 对内服务特别多:接口、数据、redis、消息队列、日志收集
Compose中有两个概念:
- 服务(service):一个应用的容器,实际上可以包括着若干运行相同镜像的容器实例
- 项目(project):由一组关联的应用容器组成的一个完整业务单元,在
docker-compose.yaml
文件中定义
使用 Docker 的时候,定义 Dockerfile 文件,然后使用 docker build
、docker run
等命令操作容器。
然而微服务架构的应用系统一般包含若干个微服务,每个微服务一般都会部署多个实例,如果每个微服务都要手动启停,这样效率很低,也不方便管理。
使用 Docker Compose 可以轻松、高效的管理容器,它是一个用于定义和运行多容器 Docker 的应用程序工具 。
yaml 官方示例:https://docs.docker.com/compose/compose-file/compose-file-v3/#compose-file-structure-and-examples
1.2 安装
curl -L "https://github.com/docker/compose/releases/download/2.2.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose # 国外的源,可以换成国内的 curl -L https://get.daocloud.io/docker/compose/releases/download/v2.2.3/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose # 修改权限 docker-compose version # 测试是否安装成功
1.3 删除
rm /usr/local/bin/docker-compose
由于 Linux 一切皆文件,删除此文件夹即可完成 Docker Compose 的卸载。
2、 使用
(base) [root@MyCentOS ~]# docker-compose --help Usage: docker compose [OPTIONS] COMMAND Docker Compose Options: --ansi string Control when to print ANSI control characters ("never"|"always"|"auto") (default "auto") --compatibility Run compose in backward compatibility mode --env-file string Specify an alternate environment file. -f, --file stringArray Compose configuration files --profile stringArray Specify a profile to enable --project-directory string Specify an alternate working directory (default: the path of the Compose file) -p, --project-name string Project name Commands: build 构建或者重构一个微服务 convert Converts the compose file to platform's canonical format cp Copy files/folders between a service container and the local filesystem create Creates containers for a service. down 停止并删除相关容器以及网络信息 events Receive real time events from containers. exec Execute a command in a running container. images 列出用来创建容器的镜像 kill 强行停止一个微服务里面的容器 logs View output from containers ls 展示所有正在运行的项目 pause 暂停微服务 port Print the public port for a port binding. ps 列出所有正在运行的容器 pull 将微服务镜像推送到远程仓库 push 将微服务镜像拉取到本地 restart 重启容器 rm 移除并且停止微服务里面的所有容器 run Run a one-off command on a service. start 启动微服务 stop 停止微服务 top 展示微服务运行的一些进程信息 unpause Unpause services up 创建并且启动一个容器 version 显示docker-compose的版本信息 Run 'docker compose COMMAND --help' for more information on a command.
3、 配置文件
其为一个yaml文件,用到了yaml文件的语法。
同时官方默认的文件名称是docker-compose.yaml
文件,如果是其他名字,则要通过-f
参数指定配置文件。
version: "3.7" # 指定docker-compose版本开头 services: # 配置微服务 postgres: # postgres数据库,服务名唯一 image: library/postgres:11.5-alpine # 指定镜像类型 container_name: postgress115 # 容器名称,不是必填,可以用于容器间的通信 --name restart: always # 启动方式 networks: # 指定的网络 --net - postgresql ports: # 端口转发 -p - 5432:5432 environment: # 环境变量,重点,向容器内部传递一些参数 --env TZ: Asia/Shanghai POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres_pass volumes: # 挂载数据卷,进行数据共享 -v - ./data:/var/lib/postgresql/data env_file: # 传入存储环境变量的文件 - ./common.env - ./apps/web.env - /opt/secrets.env volumes_from: # 从另一个服务或容器挂载它的所有卷。 - service_name - container_name pgadmin: image: dpage/pgadmin4 # 指定镜像名称 container_name: pgadmin # 容器名字 restart: unless-stopped # 不用重启 ports: # 端口映射 - 5431:80 environment: # 配置环境变量 PGADMIN_DEFAULT_EMAIL: kun@qq.com PGADMIN_DEFAULT_PASSWORD: pwd depends_on: # 启动依赖,重点 - postgres networks: # 指定网络 - postgresql redis: build: /path/to/build/dir # 指定 Dockerfile 所在文件夹的路径。 Compose 将会利用它自动构建这个镜像,然后使用这个镜像 command: "redis-server --appendonly yes" # 覆盖容器启动后默认执行的命令 expose: # 暴露端口,但不映射到宿主机,只被连接的服务访问 - "3306" volumes: # 声明上面服务所使用的自动创建的卷名 data: # 声明指令的卷名 compose自动创建该卷名,会在项目启动前加入 extranal: # 使用自定义卷名,true确定使用的卷名,注意:需要在启动容器之前,手动创建容器,如果需要其自动创建卷,需要使用false true networks: # 定义服务用到的桥 postgresql: # 定义上面的服务用到的网桥名称,默认创建的就是bridge extranal: true # 使用外部指定的网桥,注意,网桥必须要存在 name: postgresql
最核心的是
services
:
- 其实是服务之间的关系
这是一些常用的docker-compose 部署语法,更多的可以到官方文档查看
十、 部署案例
flask + mysql + redis
flask
:主程序
mysql
: 数据存储
redis
:存储session
1.1 环境准备
首先,我们写一个简易的登录验证系统
# _*_ encoding: utf-8 _*_ from flask import (Flask, request, redirect, session) from pymysql import connect from os import urandom from flask_session import RedisSessionInterface from redis import Redis app = Flask(__name__) app.secret_key = urandom(32) app.config["SESSION_USE_SIGNER"] = True app.session_interface = RedisSessionInterface( redis=Redis("flaskRedis"), key_prefix="flask_login", ) db_config = { "host": "flaskMysql", # 这个主机改为我们共享网段的路径 "port": 3306, "user": "root", "password": "qwe123", "db": "custom", "charset": "utf8" } conn = connect( **db_config ) @app.route('/login/<password>/<name>') def hello_world(password, name): cor = conn.cursor() cor.execute(r"SELECT * FROM `info` WHERE name=%s and pwd=%s", (name, password)) # cor.execute(r"SELECT * FROM `info`") if cor.fetchall(): session["user_info"] = name return "登录成功" return "账号或密码错误,登录失败" @app.route("/index") def index(): user_info = session.get("user_info") if not user_info: # 如果没有用户信息,则返回登录页面 return redirect("/login") return { "msg": 200, "data": "返回的数据,后面完善" } @app.route("/loginout") def loginout(): del session["user_info"] # 删除cookies信息,进行注销操作 return redirect("/login") # 重定向到登录页面 @app.route("/") def red(): # 如果直接访问的话,默认跳转到登录界面 return "首页面" # 进行url的跳转 if __name__ == '__main__': app.run(host="0.0.0.0", port=8888)
同时,也要事先写好我们的SQL文件,我们将其放入/init/custom.sql
,以便对数据库的快速初始化:
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; DROP TABLE IF EXISTS `info`; CREATE TABLE `info` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, `pwd` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact; INSERT INTO `info` VALUES (1, 'Make', '123456'); INSERT INTO `info` VALUES (2, 'lis', 'qwe123'); SET FOREIGN_KEY_CHECKS = 1;
包依赖:requirements.txt
Flask-Session==0.4.0 Flask==2.1.3 PyMySQL redis==4.3.4
然后,我们将这些文件一同提交到服务器中
1.2 Dockerfile
FROM python:3.9 MAINTAINER kun<350051500050@qq.com> ENV MYPATH /usr/src/app/ WORKDIR $MYPATH COPY . $MYPATH RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.douban.com/simple CMD python init.py && python app.py #
1.3 自定义网络
使得不同容器之间可以实现网络通信
docker network create --subnet 192.169.0.0/16 --gateway 192.169.0.1 flasknet
1.4 docker-compose.yaml
version: "3.7" services: mysql1: # 配置一个数据库 image: mysql:5.7 # 拉取的镜像版本 container_name: flaskMysql # 定义数据库的名字 restart: always # 是否重启 networks: # 网络连接 - flasknet environment: # 环境配置 MYSQL_ROOT_PASSWORD: qwe123 # 数据库登录密码 MYSQL_DATABASE: custom # 会创建这个数据库 volumes: # 挂载的卷 - mysqlconf:/etc/mysql/confi.d - mysqldata:/var/lib/mysql - ./init/:/docker-entrypoint-initdb.d/ # docker-entrypoint-initdb.d,mysql启动时会扫描这个目录,运行脚本 redis1: # 配置redis image: redis # 拉取redis镜像 container_name: flaskRedis # 这个数据的名称,类似于域名 restart: always # 是否进行重启 networks: # 网络配置,使得容器之间网络可以连通 - flasknet volumes: # 挂载数据卷 - redisconf:/etc/redis/redis.conf - redisdata:/data app: build: # 通过Dockerfile创建镜像 . container_name: flaskService # 对容器命名 restart: always # 是否进行重启 networks: # 网络配置 - flasknet volumes: # 挂载的数据卷 - /root/flask-test/:/usr/src/app/ depends_on: # 配置依赖环境 - mysql1 - redis1 ports: # 端口映射 - 8888:8888 networks: # 声明我们的网络,同时,这个网络是我们自己创建的 flasknet: external: true volumes: # 声明数据卷,具名数据卷自动创建,不需要我们手动创建 mysqlconf: mysqldata: redisdata: redisconf:
新知识点:
MYSQL_DATABASE
:设置mysql在启动时需要创建的数据库./init/:/docker-entrypoint-initdb.d/
: 数据库初始化脚本,当Mysql容器首次启动时,会在/docker-entrypoint-initdb.d
目录下扫描 .sh,.sql,.sql.gz类型的文件。如果这些类型的文件存在,将执行它们来初始化一个数据库注意:
- 如果使用了匿名数据卷,要保证映射的匿名数据卷的数据目录是空的,不然数据库无法运行新的脚本
- 所以,执行docker-compose up 命令之前,要清空映射mysql数据库文件的目录哦,有时候还会存在隐藏文件,也要删掉(rm -rf 执行时要注意执行的目录是否正确)!
本文来自博客园,作者:Kenny_LZK,转载请注明原文链接:https://www.cnblogs.com/liuzhongkun/p/16791244.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 上周热点回顾(2.17-2.23)
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)