微服务必备容器化技术

docker介绍与安装及上手应用

什么是容器化技术?为什么需要学习docker?

在微服务的体系架构中,因为应用程序会进行拆分,这时就会存在多个服务需要部署运行,相应的多个服务之间具有多种部署方案,这时传统的方式就会面临巨大的挑战。

在特殊时候需要动态的并快速的新增服务或减少服务,例如在秒杀抢购服务在双十一的时候才具有较大的并发流量,流量可能是平时的好几倍需要做好扩容,但是在平时又不存在这么多的流量,因此对整个程序就需要做到自适应伸缩扩容。

就有提出采用虚拟化技术,其代表的产品如VMWare,但是该方案存在问题,就是系统的启动运行需要较长的时间,因此在当时业界就希望有一种轻量级的虚拟化技术来解决这个问题,顾就提出了容器化技术。

容器化技术是一种轻量级的虚拟化技术,它利用操作系统级别的虚拟化来隔离应用程序和它们的依赖。容器化技术使用容器引擎(如Docker)来创建和管理容器,每个容器都运行在共享的操作系统内核上,可以共享主机的资源,而目前容器化技术中docker被广为熟知。

如何理解docker

在我们做项目的时候原本我们是定义为,一个应用程序对应 一个系统。

现在改为:

go 复制代码
应用程序  → 特定的docker容器 → 部署到系统中

如下是docker的图标,一条鲸鱼+N个集装箱,实际上这个图标就已经很好的告诉我们docker的寓意和应用

docker可以理解为是一个应用程序环境的打包运行工具,比如我们运行redis所依赖的核心程序一起打包成一个封装的独立的集中箱,然后通过docker可以移植部署在不同的环境系统上。这样在部署上就得到了统一,提高了开发和运维的交互效率。

而docker除了这样的功能外,它呢在容器启动停止的时候也是非常快速的,这样的话如果服务需要在某一个时候动态伸缩服务的时候就可以得到解决。

总结docker主要实现的功能

  1. 更高效的利用系统资源
  2. 更快速的启动时间
  3. 一致的运行环境
  4. 持续交付和部署
  5. 更轻松的迁移
  6. 更轻松的维护和扩展

docker下载与安装

shell 复制代码
# 安装依赖
yum install -y yum-utils device-mapper-persistent-data lvm2
# 添加软件源信息
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# 更新并安装Docker-CE --- 可以跳过不执行
yum makecache fast

yum list docker-ce --showduplicates|sort -r
# 默认安装最新版本
yum -y install docker-ce
# 配置docker镜像源和cgroup
mkdir /etc/docker/
touch /etc/docker/daemon.json
cat > /etc/docker/daemon.json << EOF
{
    "exec-opts": ["native.cgroupdriver=systemd"],
    "registry-mirrors": ["https://hub-mirror.c.163.com"]
}
EOF
systemctl enable  docker --now

docker system prune

docker的基础组成

  • 宿主机:docker本质就是一个程序,它运行在那个系统上,那个系统就是它的宿主机
  • 容器:一个容器就是一个小的微型系统只有开发需要的依赖可以是运行也可以是非运行的状态
  • 镜像:如果想要创建容器就需要一个镜像,而所谓的镜像可以看成是一份编译好的代码程序,可以这么理解。准确一点理解是一个只读的模板,一个独立的文件系统,包括运行容器所需的数据,可以用来创建新的容器。
  • 仓库:和GitHub/gitee类似,只不过它上面存放的是镜像,你的镜像根文件就是来源这里,当然我们也可以自己发布自己的镜像

dockerfile: 镜像文件的代码版本,我可以通过编写代码创建想要的镜像。

docker体验

docker安装完成后,在命令行执行docker,如果能够看到命令提示则说明docker安装成功。我们可以通过通过doker创建一个安装好redis的容器。

仓库地址:hub.docker.com

shell 复制代码
# docker pull 是从仓库上下载容器; 在pull后则就是具体要下载的容器对象
docker pull redis:alpine3.18

# docker images 查看系统的镜像
docker images

# 通过docker run 运行启动
docker run -p 16379:6379 --name=redis -d redis:alpine3.18

# docker ps 查看正在运行的容器, 后跟 -a 则查询所有的包含停止的容器
docker ps 

解释:docker run帮助我们在宿主机中创建一个redis的容器,这个容器呢可谓是"麻雀虽小五脏俱全"在容器中会运行一个系统可以是centos也可以是ubuntu.当前我们使用的是一个叫alpine的系统,比centos和ubuntu要小。

接下来看看docker run后面的命令参数-p 16379:6379 --name=redis -d redis:alpine3.18

  • 最后一个redis:alpine3.18 基本是固定的写法,即容器镜像,也就是docker pull 后指定的内容
  • -d 表示运行方式,我们让容器处于后台运行,而不是挂起在当前的命令行下运行
  • --name 指创建的容器名称,当前指定容器的名称为redis
  • -p 用于绑定容器与宿主机的端口,也就是第一个参数是属于宿主机的第二个属于容器的,也就是当我们访问16379的时候就会访问到redis容器的6379端口的服务。

dockerfile介绍并创建go-zero环境容器

docker的基础组成

  • 宿主机:docker本质就是一个程序,它运行在那个系统上,那个系统就是它的宿主机
  • 容器:一个容器就是一个小的微型系统只有开发需要的依赖,可以是运行也可以是非运行的状态
  • 镜像:如果想要创建容器就需要一个镜像,而所谓的镜像可以看成是一份编译好的代码程序,可以这么理解。准确一点理解是一个只读的模板,一个独立的文件系统,包括运行容器所需的数据,可以用来创建新的容器。
  • 仓库:和GitHub/gitee类似,只不过它上面存放的是镜像,当然我们也可以自己发布自己的镜像
  • dockerfile: 镜像文件的代码版本,我可以通过编写代码创建想要的镜像。

从容器构建属于go环境的容器

在dockerfile的学习中遇到过很多的同学对其概念以及dockerfile构建中存在的问题,及dockerfile的运行本质并不是很了解,因此在学习dockerfile前,我们先自己通过以存在的容器构建go环境的容器。

过程:

  1. 选择好要构建go容器环境的系统
  2. 先拉取系统镜像
  3. 然后运行系统镜像,构建出容器
  4. 再进入容器安装go

选择好要构建go容器环境的系统

系统的选择很讲究,因为对docker来说,它是依托于宿主机运行的,同时我们也希望它能够启动运行足够快并且还期望它不占用太多的系统资源,顾选择较小的系统就有这块的优势。

这里我们用alpine系统作为go环境的运行系统,因为它比较小,大约7M的左右。

先拉取系统镜像

go 复制代码
docker pull alpine:3.18

然后运行系统镜像,构建出容器

go 复制代码
docker run -p 8080:80 --name go -d alpine:3.18

在构建容器的时候与宿主机绑定8080端口,可便于后续容器构建好之后的测试

再进入容器安装go相关所需要的环境

go 复制代码
docker exec -itd 容器名 执行命令[sh]
# OPTIONS说明:
-d :分离模式: 在后台运行
-i :即使没有附加也保持STDIN 打开
-t :分配一个伪终端

我在命令后使用sh,代表在终端中进行交互,sh是docker绝大数容器都是使用的交互命令。

shell 复制代码
[root@192 ~]# docker exec -it go sh
Error response from daemon: Container 5dc7cf5ac8478f36d0dfc9e6c68e84df306af8cfdb0e1a79be6b0978a3c80307 is not running

但是当我们用docker exec 进入go容器的时候出现,go容器没有在运行,为什么?这是和docker的运行有关,docker对于启动的容器要求必须存在一个能够挂起运行的状态,否则程序启动即停止。

这很好理解因为对于docker来说它验证的容器是必须能够一直运行的,但是呢我们创建的容器因为没有挂起程序所以对docker来说相当于没有运行。

go 复制代码
# 先删除go容器,然后重新docker run创建
docker rm go

# 在docker run的时候在命令的最后增加一个一定能挂起运行的命令,比如ping
docker run -p 8080:80 --name go -d alpine:3.18 ping www.baidu.com

# 再docker ps 查看即可发现存在
docker ps 

这个时候我们继续使用docker exec -it go sh命令进入到go容器中,完成go环境的搭建。

进入容器安装go

在进入到容器中安装go的时候,我们需要注意一点,即就是将安装过程所用到的命令和操作记录起来,因为在dockerfile中也会用到这些命令来构建容器。

shell 复制代码
mkdir /go
cd /go
wget --no-check-certificate https://golang.google.cn/dl/go1.21.0.linux-amd64.tar.gz
tar -C /usr/local -zxf go1.21.0.linux-amd64.tar.gz

# 此处我们需要删除已经解压后的go包,因为对于docker来说下载下来的文件也会影响到它的大小
# 因我们期望docker是足够的小,顾对于一些无用的软件文件是建议删除卸载
rm -rf /go/go1.21.0.linux-amd64.tar.gz 

# 注意!这一步是因为alpine系统对go程序运行的时候默认查找的类库与提供的类库不一致
# 故需要做此操作。
mkdir /lib64
ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2

# 配置go的系统环境
export GOPATH=/go
export PATH=/usr/local/go/bin:$GOPATH/bin:$PATH

# 验证
go version
go version go1.21.0 linux/amd64

基于dockerfile构建go容器镜像

在上面的过程中,我们是先构建好一个初始化的alpine系统,然后进入到alpine系统中逐步安装go,但在应用开发中上面的构建过程大都是相同的,如何提升效率呢?

这个时候我们就可以通过dockerfile帮助我们提高构建的效率,它的工作方式就是将我们要在容器中执行的命令操作,事先写在dockerfile中,docker可以识别dockerfile中的内容,然后把它们翻译成容器会执行的命令,然后准备执行命令并最终构建好一个镜像,我们再根据镜像启动容器。

这样做的好处就是,用户只需要调整dockerfile文件中的命令,即可构建不同的容器,大大提供了构建容器的效率,同时因为是文件的关系其他人也可以通过dockerfile了解到系统是如何搭建程序的。

dockerfile 复制代码
# 我们需要引入到基础容器
FROM alpine:3.18

# 注意看这里我们的写法, 在sh中 && 可以表示下一条命令连续执行,而 \ 则是命令的分隔符号
# 思考:为什么这么写?
RUN mkdir /go && cd /go \
    && wget --no-check-certificate https://golang.google.cn/dl/go1.21.0.linux-amd64.tar.gz \
    && tar -C /usr/local -zxf go1.21.0.linux-amd64.tar.gz \
    && rm -rf /go/go1.21.0.linux-amd64.tar.gz \
    && mkdir /lib64 \
    && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2 
   
# 配置系统环境变量
# 在dockerfile中对容器的系统环境变量配置统一采用ENV这个关键词定义
ENV GOPATH /go
ENV PATH /usr/local/go/bin:$GOPATH/bin:$PATH

# 这个命令可以让我们的docker容器在启动的时候就执行下面的命令
# 把原本在docker run中的命令放到dockerfile中,并示意启动容器的时候执行
# 但是如果在docker run后跟新的命令会代替CMD中的命令
CMD ["ping", "www.baidu.com"]

补充注意,在dockerfile中的注释前不能有空格;然后通过如下命令构建docker容器。

shell 复制代码
# docker build 即根据dockerfile构建镜像,-t 是指构建容器的名称格式是 name:tag 或 name
# . 是指使用当前目录下的dockerfile构建,也可以通过 -f 参数指定
docker build -t go .

# 检查是否构建好
docker images

# 如果存在构建不好或者多余的镜像可以通过如下命令删除,-f 是强制删除
docker rmi 镜像名 

# 根据镜像构建容器
docker run --name go -d go

# 然后我们可以执行下面的命令测试
docker exec -it go go

通过容器运行go-zero

我预先调整了user/api中的代码,使得api暂时不关联其他程序。

go 复制代码
import (
	"github.com/zeromicro/go-zero/rest"
	"demo/user/api/internal/config"
	"demo/user/api/internal/middleware"
	"demo/user/rpc/userclient"
)

type ServiceContext struct {
	Config            config.Config
	UserClient        userclient.User
	LoginVerification rest.Middleware
}

func NewServiceContext(c config.Config) *ServiceContext {
	return &ServiceContext{
		Config: c,
		//UserClient:        userclient.NewUser(zrpc.MustNewClient(c.UserRPC)),
		LoginVerification: middleware.NewLoginVerificationMiddleware().Handle,
	}
}

func (l *UserLogic) User(req *types.UserReq) (resp *types.UserResp, err error) {
	// todo: add your logic here and delete this line
	return &types.UserResp{
		Id:    "666",
		Name:  "木兮老师",
		Phone: "13011110000",
	}, nil
}

因为对当前的我们来说,先学习如何通过容器运行go-zero是关键,而增加复杂度反而效果不佳,所以先使得api服务不与其他服务关联,后面会讲如何关联访问。

我们将go-zero上传到服务中

go 复制代码
//这是我上传的目录地址

/go/user

我们构建一个新的容器,用于启动并访问go-zero

go 复制代码
docker run -p 8888:8888 -v /go/user:/go/src/demo/user --name go-zero -d go 

在上面的命令中通过-p绑定容器与宿主机的端口,而-v是绑定容器与宿主机共享的目录,也就是这个目录下的内容会影响到双方,称之为数据卷。

然后进入容器

shell 复制代码
# 先执行如下命令
go env -w GOPROXY=https://goproxy.io
# 然后下载依赖
go mod tidy
# 运行
cd api/
go run .

然后测试即可

基于打包部署

除了上面的方式外我们还可以基于编译程序运行,这种会相对简单,即将go-zero预先编译打包,然后通过dockerfile提前复制到容器中并启动的时候执行。这种方式我们可以应用在服务发布测试或者生产的时候。

go 复制代码
o'o# CGO_ENABLED=0 
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o bin/user-api ./api/user.go

然后我们新建一个新的dockerfile,在新的dockerfile中我们可以选择继续基于go这个镜像构建也可以基于alpine镜像构建,但目前我们采取的方式是使用编译后go-zero项目的二进制文件部署因此可以直接用alpine镜像最佳,因为不需要go的环境

dockerfile 复制代码
FROM alpine:3.18

# 这个关键词的意思是复制的意思,可以将宿主机中的内容复制到容器中
# 命令 左边是宿主机的目录,右边是容器目录
RUN mkdir /user && mkdir /user/bin && mkdir /user/conf

# 复制编译后的二进制文件
COPY bin/user-api /user/bin/
# 复制配置文件
COPY api/etc/user.yaml /user/conf/

# 为二进制提供执行权限
RUN chmod +x /user/bin/user-api

# 该命令指定容器会默认进入那个目录,如我们每次进入服务器的时候会自动进入root目录一样的作用
WORKDIR /user

# 这个命令可以让我们的docker容器在启动的时候就执行下面的命令
# 与CMD不同之处是,在docker run 后跟的命令不能替换它,它仍然会启动的时候执行
ENTRYPOINT ["bin/user-api", "-f","/user/conf/user.yaml"]

构建容器

shell 复制代码
docker build -t user-api .
docker run -p 8888:8888 --name go-zero -d user-api

小结

本节主要讲解dockerfile的使用,在dockerfile使用过程中大家可能会遇到一些问题

问题1:dockerfile构建过程中出现问题如何解决

解:这个问题在构建的时候可以先构建好dockerfile中使用的基础镜像,然后再把dockerfile中使用的命令放到容器中逐条执行验证

问题2:概念误解,如我构建一个go的容器,有的同学可能会误以为这是go的系统

docker-compose编排

容器网络

我们先了解容器的网络,在后续的使用中,我们会涉及到容器的网络应用,它是docker体系知识中一个重要的知识点。

docker在安装后运行的时候会默认创建三个网络,我们可以通过docker network ls查看所有的网络。

shell 复制代码
[root@192 ~]# docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
252a2ca6302a   bridge    bridge    local
65a576c82770   host      host      local
e0a7aac94266   none      null      local

none模式

在这种模式下容器会独立network,但并没有对其进行任何的网络设置,如分配ip等。

bridge模式

在该模式中,Docker 会创建一个虚拟以太网桥 docker0,新建的容器会自动桥接到这个接口,然后docker会依据docker0在创建的时候设立的网络段给容器分配ip。

但默认的方式存在一个问题,就是每次重启docker默认的网络段也会发生变化,而采用该网络段的docker容器也会跟着发生变化。当然我们也可以自定义一个网络段,然后自己给创建的容器分配指定的ip。

shell 复制代码
# 命令格式
docker network create --subnet=<subnet> <network_name>
docker network create --subnet=172.0.0.0/24 net-test

# 查看网络段详情
docker network inspect net-test

# 删除网络段
docker network rm net-test

# 给容器分配网络段
docker run --name go-net-test --ip 172.0.0.2 --net net-test -d go

# 结果
[root@192 ~]# docker exec -it go-net-test ipaddr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN 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
43: eth0@if44: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP 
    link/ether 02:42:ac:00:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.0.0.2/24 brd 172.0.0.255 scope global eth0
       valid_lft forever preferred_lft forever
[root@192 ~]# 

host模式

  • host 网络模式需要在创建容器时通过参数 --net host 或者 --network host 指定;
  • 采用 host 网络模式的 Docker Container,可以直接使用宿主机的 IP 地址与外界进行通信,若宿主机的 eth0 是一个公有 IP,那么容器也拥有这个公有 IP。同时容器内服务的端口也可以使用宿主机的端口,无需额外进行 NAT 转换;
  • host 网络模式可以让容器共享宿主机网络栈,这样的好处是外部主机与容器直接通信,但是容器的网络缺少隔离性。

docker容器互通

再看看docker容器的互通,即在docker中内部之间又如何互通呢?在容器中的连接通讯方式我们可以采用三种方式

  1. 通过宿主机做桥接
  2. 容器都在同一网络段
  3. 通过link命令

1.通过宿主机做桥接

这种方式的话就是在构建容器的时候与宿主机绑定端口,对外暴露服务的方式。

go 复制代码
docker run -p 80:80 -d go

容器内部之间的请求先走宿主机,然后再通过宿主机访问到容器,一般在跨服务的时候应用。

2.容器都在同一网络段

通过自定义网络段,然后让创建的容器都处于在这个网络段中,容器之间就可以通过分配好的网络ip相互之间即可访问。如下哎例子

go 复制代码
docker run --name go-net-test3 --ip 172.0.0.3 --net net-test -d go

[root@192 ~]# docker exec -it go-net-test sh
/ # ping 172.0.0.3
PING 172.0.0.3 (172.0.0.3): 56 data bytes
64 bytes from 172.0.0.3: seq=0 ttl=64 time=0.202 ms
64 bytes from 172.0.0.3: seq=1 ttl=64 time=0.123 ms

这种方式可以应用在不同的服务类型中。

3.通过link命令

我们可以在构建容器的时候添加一个--link的指令,使得容器在构建出来后,就可以与指定的容器进行绑定。两个容器之间可以通过容器名相互访问,但是这种方式仍然需要在同一个网络段下,默认使用的是docker0这个网络段。

shell 复制代码
docker run --name go-net4 --link go-net-test --net net-test -d go

[root@192 ~]# docker exec -it go-net-test sh
/ # ping go-net4
PING go-net4 (172.0.0.4): 56 data bytes
64 bytes from 172.0.0.4: seq=0 ttl=64 time=0.203 ms
64 bytes from 172.0.0.4: seq=1 ttl=64 time=0.213 ms

因此3需要基于2的基础上进行实现,在使用上3会有较好的优势,因为无需关注具体的网络ip,可以之间通过容器名进行访问。

部署api/rpc服务通讯

接着上次的内容将rpc服务通过docker部署并实现连调,在代码层面注意修改api服务在核心代码层对rpc客户端的初始化

go 复制代码
type ServiceContext struct {
	Config            config.Config
	UserClient        userclient.User
	LoginVerification rest.Middleware
}

func NewServiceContext(c config.Config) *ServiceContext {
	return &ServiceContext{
		Config:            c,
		UserClient:        userclient.NewUser(zrpc.MustNewClient(c.UserRPC)),
		LoginVerification: middleware.NewLoginVerificationMiddleware().Handle,
	}
}

然后调整一下rpc中的数据来源,暂时不来源redis和mysql这将作为练习尝试。在开始的时候注意还需要先pull一个etcd,作为服务发现注册机制。

shell 复制代码
# 先停止所有容器,并进行删除
docker ps -a -q | xargs docker stop | xargs docker rm

如下是关于etcd的dockerfile_etcd

dockerfile 复制代码
FROM bitnami/etcd:3.4.15

ENV ETCD_ENABLE_V2 true
ENV ALLOW_NONE_AUTHENTICATION yes
ENV ETCD_ADVERTISE_CLIENT_URLS http://etcd:2379
ENV ETCD_LISTEN_CLIENT_URLS http://0.0.0.0:2379
ENV ETCD_NAME etcd

构建镜像

shell 复制代码
docker build -t etcd -f ./Dockerfile_etcd .

docker run -d -p 2379:2379 -p 2380:2380 --net user --ip 168.10.0.20 --name etcd etcd

构建过程

  1. 构建好user-rpc的dockerfile
  2. 定义好桥接网络段
  3. 部署服务并使用该网段
  4. 通过容器名访问服务

构建好user-rpc的dockerfile

使用二进制的方式

dockerfile 复制代码
FROM alpine:3.18

# 这个关键词的意思是复制的意思,可以将宿主机中的内容复制到容器中
# 命令 左边是宿主机的目录,右边是容器目录
RUN mkdir /user && mkdir /user/bin && mkdir /user/conf

# 复制编译后的二进制文件
COPY bin/user-rpc /user/bin/
# 复制配置文件
COPY rpc/etc/user.yaml /user/conf/

# 为二进制提供执行权限
RUN chmod +x /user/bin/user-rpc

# 该命令指定容器会默认进入那个目录,如我们每次进入服务器的时候会自动进入root目录一样的作用
WORKDIR /user

# 这个命令可以让我们的docker容器在启动的时候就执行下面的命令
# 与CMD不同之处是,在docker run 后跟的命令不能替换它,它仍然会启动的时候执行
ENTRYPOINT ["bin/user-rpc", "-f","/user/conf/user.yaml"]

注意修改配置

yaml 复制代码
Name: User
Host: 0.0.0.0
Port: 8888
UserRPC:
  Etcd:
    Hosts:
      - etcd:2379
    Key: user.rpc
yaml 复制代码
Name: user.rpc
ListenOn: 0.0.0.0:8080
Etcd:
  Hosts:
  - etcd:2379
  Key: user.rpc
Mysql:
  DataSource: root:000000@tcp(127.0.0.1:3306)/im?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
Cache:
  - Host: 127.0.0.1:6379
    Type: node
    Pass:

构建好user-rpc并重新构建user-api

shell 复制代码
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o bin/user-api ./api/user.go
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o bin/user-rpc ./rpc/user.go


docker build -t user-api -f ./Dockerfile .
docker build -t user-rpc -f ./Dockerfile_rpc .

定义好桥接网络段

shell 复制代码
docker network create --subnet=175.0.0.0/24 user

部署服务并使用该网段

shell 复制代码
docker run --name etcd -p 2379:2379 --net user --ip 168.10.0.100 -d etcd
docker run --name user-rpc -p 8080:8080 --net user --ip 168.10.0.70 --link etcd -d user-rpc
docker run --name user-api -p 8888:8888 --net user --ip 168.10.0.50 --link etcd -d user-api

# etcd查询所有的key
etcdctl get --prefix ""

docker ps

docker-compose

在前面的内容中我们基本上就已经实现了服务的部署,但是在过程中存在问题,即我们可以看到在构建api/rpc/etcd容器的时候实际上过程是比较繁琐的,并且又具有较多重复性的工作,这个时候我们的需求就是期望有一个工具可以很好的帮助我们管理容器,可以一键启动所有容器一键停止所有容器,快速配置各个容器的命令参数。

这个时候我们就可以用到docker编排工具docker-compose,它是一个用于定义和运行多容器 Docker 应用程序的工具。它允许您使用一个简单的 YAML 文件来配置应用程序的各个服务,并通过一条命令启动、停止和管理这些服务。

通过使用 Docker Compose,您可以轻松地构建和管理应用程序的多个服务,例如数据库、Web 服务、消息队列等。它利用了 Docker 强大的容器化技术,使得应用程序的部署和扩展变得更加简单和可靠。

安装

shell 复制代码
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

docker-compose

# 离线方式
# 从github上下载对应版本
mv docker-compose-linux-x86_64 /usr/local/bin/
cd /usr/local/bin/
//修改文件名
mv docker-compose-linux-x86_64 docker-compose

//授权
sudo chmod +x /usr/local/bin/docker-compose

//查看安装是否成功
docker-compose -v

配置docker-compose.yaml

shell 复制代码
mkdir -p /etcd/data
mkdir -p /etcd/logs

chmod -R 777 ./etcd/data
chmod -R 777 ./etcd/logs
yaml 复制代码
version: "3"

services:
  # 服务名
  etcd:
    # 选择镜像:镜像的选择会先从本地找,如果没有会去仓库中拉取下来
    image: etcd
    # 可以选择指定的dockerfile自动帮我构建
    build:
      # 指定dockerfile所在的目录
      context: ./ 
      # 指定dockerfile的文件名
      dockerfile: dockerfile_etcd
    # 定义创建的容器名
    container_name: etcd
    # 使创建的容器与宿主机绑定端口
    ports:
      - "2379:2379"
      - "2380:2380"
    # 配置系统环境变量  
    environment:
      - ETCD_ENABLE_V2=true
      - ALLOW_NONE_AUTHENTICATION=yes
      - ETCD_ADVERTISE_CLIENT_URLS=http://etcd:2379
      - ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
      - ETCD_NAME=etcd
    # 配置容器与宿主机的共享目录  
    # 同时需要注意宿主机存在目录,并且要基于权限不然系统会直接报错
    volumes:
      - ./etcd/data:/bitnami/etcd/data
      - ./etcd/logs:/bitnami/etcd/logs
    # 设置容器的网络段  
    networks:
      guser:
   
  user-rpc:
    image: user-rpc
    container_name: user-rpc
    ports:
      - "8080:8080"
    # 与etcd服务互通  
    links:
      - etcd
    # 需要等指定容器启动后才可以启动,填写的是容器的服务名
    depends_on:  
      - etcd
    networks:
      guser:
      
  user-api:
    image: user-api
    container_name: user-api
    ports:
      - "8888:8888"
    # 与etcd服务互通  
    links:
      - etcd
    # 需要等指定容器启动后才可以启动,填写的是容器的服务名
    depends_on:  
      - etcd
      - user-rpc
    networks:
      guser:      
# 由docker创建      
networks:
  guser:
    driver: bridge

总结

在本节中主要讲解docker的网络以及docker-compose,通过docker部署好user的rpc与api服务,

在内本节中大家可能会遇到的问题有

容器之间无法实现互通

这个问题是很多初学者学习docker的时候经常会遇到的问题,解决思路。

  1. 首先梳理自己的镜像构建过程,容器的启动命令及过程,看看是否存在配置上 的问题
  2. 然后再看自己在配置和构建的时候api、rpc的配置信息是否有误
  3. 在调试中,你可以先部署一个服务,然后进入容器中通过利用ping检查是否可以访问到目标容器,然后用curl检测是否可以访问到容器中的程序【注意两个命令的不同哟】
  4. 问题往往会出现在如下情况
    1. 配置不正确
    2. 网络段不一致
    3. 容器中的服务没有启动
    4. 如果设置过自己宿主机的防火墙则需要重启docker

关于docker中的共享目录

在构建docker容器的时候,我们不能把它当做一个普通的程序看待,而是作为一个随时可运行可删除的程序来看待,当docker停止删除随之也会删除docker容器这个过程中在容器内部产生的数据,因此在程序中使用docker的时候,我们需要思考容器的数据是否需要保留以及重要性,如mysql、redis在用docker的时候就需要配置好数据卷共享目录,以免docker容器的停止运行造成数据的丢失问题。

相关推荐
man20171 小时前
【2024最新】基于springboot+vue的闲一品交易平台lw+ppt
vue.js·spring boot·后端
hlsd#2 小时前
关于 SpringBoot 时间处理的总结
java·spring boot·后端
路在脚下@2 小时前
Spring Boot 的核心原理和工作机制
java·spring boot·后端
幸运小圣2 小时前
Vue3 -- 项目配置之stylelint【企业级项目配置保姆级教程3】
开发语言·后端·rust
前端SkyRain3 小时前
后端Node学习项目-用户管理-增删改查
后端·学习·node.js
提笔惊蚂蚁3 小时前
结构化(经典)软件开发方法: 需求分析阶段+设计阶段
后端·学习·需求分析
老猿讲编程3 小时前
Rust编写的贪吃蛇小游戏源代码解读
开发语言·后端·rust
黄小耶@3 小时前
python如何使用Rabbitmq
分布式·后端·python·rabbitmq
宅小海5 小时前
Scala-List列表
开发语言·后端·scala
蔚一5 小时前
Javaweb—Ajax与jQuery请求
前端·javascript·后端·ajax·jquery