DockerSwarm上写一个聊天室应用chitchat

这篇文章主要介绍“Docker Swarm上写一个聊天室应用chitchat”,在日常操作中,相信很多人在Docker Swarm上写一个聊天室应用chitchat问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Docker Swarm上写一个聊天室应用chitchat”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

创新互联公司,为您提供网站建设公司成都网站制作、网站营销推广、网站开发设计,对服务混凝土搅拌罐车等多个行业拥有丰富的网站建设及推广经验。创新互联公司网站建设公司成立于2013年,提供专业网站制作报价服务,我们深知市场的竞争激烈,认真对待每位客户,为客户提供赏心悦目的作品。 与客户共同发展进步,是我们永远的责任!

Phoenix web应用和其他语言/框架实现的web应用相比最大的不同点在于,Phoenix应用能在有状态的情况下依然保持很好的横向扩展能力,这得益于其底层的Erlang OTP支持。为了能在集群中的各节点之间共享状态,各节点只需相互认识即可,并不需要单独开一个状态容器(如redis),这也使得Phoenix应用的架构更为简单明了。Elixir 1.9更加入了对release的支持,也使得打包部署更加方便。但是,这些都仅限于传统的、预先知道集群容量和各节点IP的部署方式。如何能在Docker Swarm上,在不预先知道各节点IP的情况下部署Phoenix应用并做到动态扩容成了下一个挑战。今天就来尝试部署一下最典型的有状态web应用——基于WebSocket的聊天室。

写一个聊天室应用chitchat

因为不是重点所以不写了。如果你不会写,直接去GitHub上拉代码

Release准备

这里只做最简单的准备。

$ mix release.init

我们还需要在config/prod.secret.exs里加一行代码让我们release出来的包(artifact)知道要启动所有相关的application。

config :chitchat, ChitchatWeb.Endpoint, server: true

创建Docker镜像

在项目的根目录下创建Dockerfile,并加入以下内容:

FROM elixir:1.9.1-alpine as build

# install build dependencies
RUN apk add --update git build-base nodejs npm yarn python

# prepare build dir
RUN mkdir /app
WORKDIR /app

# install hex + rebar
RUN mix local.hex --force && \
    mix local.rebar --force

# set build ENV
ENV MIX_ENV=prod

# install mix dependencies
COPY mix.exs mix.lock ./
COPY config config
RUN mix deps.get
RUN mix deps.compile

# build assets
COPY assets assets
RUN cd assets && npm install && npm run deploy
RUN mix phx.digest

# build project
COPY priv priv
COPY lib lib
RUN mix compile

# build release
COPY rel rel
RUN mix release

# prepare release image
FROM alpine:3.9 AS app
RUN apk add --update bash openssl

RUN mkdir /app
WORKDIR /app

COPY --from=build /app/_build/prod/rel/chitchat ./
RUN chown -R nobody: /app
USER nobody

ENV HOME=/app

这是从Phoenix官方文档里直接复制过来的,除了改了一下应用名称和在apk add里添加了npm以及把COPY rel rel放出来以外什么都没改。

这是一个multi-stage的Dockerfile,为了使最终生成的镜像尽可能小,我们把Elixir、Mix、node.js等运行时不需要的东西全都留在了build阶段的镜像里,只把最终release出来的东西(包含Erlang运行时)放进了最终镜像。我这里构建出来的docker镜像约35MB。虽然现在已经能构建了,但我暂时不构建。

创建Swarm

为了图方便,我只做了单节点swarm:

$ docker swarm init

如果你手上有3台以上的电脑,你也可以做全尺寸swarm。这不是重点所以略过。如果你不知道怎么做,参考官方教程。

本地化Docker Registry

由于部署到swarm集群里的服务必须使用预先构建好的镜像(如果每个节点各自构建镜像又慢又耗资源),而实际生产环境下每个镜像可能会很大(上G),所以我们需要一个在内网里的Docker Registry来注册并在各个节点上共享镜像。

在任意manager节点上运行

$ docker service create --name registry -p 5000:5000 registry:2

这一句会在你的swarm里创建一个名为registry的服务,用的镜像是Docker官方的registry:2,公开5000端口。它只有一个replica。

构建镜像并推上Registry

运行命令

$ docker build --tag 127.0.0.1:5000/chitchat:0.1.0 .

即可构建出镜像。版本号最好和mix.exs里的保持一致。注意,Docker Registry貌似不会覆盖已有镜像(待考证),所以版本号最好不要用latest。127.0.0.1:5000是registry的IP地址和端口号,根据你的swarm的实际情况改之。构建完后运行命令

$ docker push 127.0.0.1:5000/chitchat:0.1.0

就能将这个镜像推到本地的registry上了。

docker-compose.yml

在项目的根目录下创建docker-compose.yml,并添加下列内容:

version: '3.7'

services:
  app:
    image: 127.0.0.1:5000/chitchat:0.1.0
    ports:
      - 80:4000
    entrypoint: ./bin/chitchat start
    deploy:
      mode: replicated
      replicas: 3

除了deploy项之外,这可以算是最简单的docker-compose配置文件了。先跑跑看

$ docker-compose up

它应该能直接跑起来(虽然会有警告说deploy项无效),访问80端口、连接ws应该都没问题。

接着我们尝试部署到swarm上(单节点的同学记得把刚才的试运行关掉哦):

$ docker stack  deploy -c ./docker-compose.yml chitchat

确认服务都起来了

$ docker stack services chitchat

应该看到如下内容:

ID                  NAME                MODE                REPLICAS            IMAGE                           PORTS
x2eym27lc2b8        chitchat_app        replicated          3/3                 127.0.0.1:5000/chitchat:0.1.0   *:80->4000/tcp

如果看到REPLICAS是3/3,说明部署成功,如果一直是0/3,则检查你的代码有没有问题。

为了接下来的调试,先跟踪一下日志:

$ docker service logs -f chitchat_app

然后打开两个浏览器窗口/标签,访问一下 http://127.0.0.1/rooms/1 ,看一下日志确保ws连接到了不同的replica上,如果连在了同一个上面,则刷新其中一个窗口,直到它连到了不同的replica为止。发一条消息试试,你会发现 另一个窗口收不到消息!

问题出在哪儿了?问题出在各个节点上的epmd(Erlang Process Manager)各自为政,没有连接到一起。所以下一步就是想办法把它们连起来。

我们知道Elixir有一个函数Node.connect/1可以连接到其他节点,只要它们有相同的cookie。问题在于,这种连接方式需要预先知道对方的IP或域名或主机名。但是在一个容器编排系统(container orchestration system)里,容器的IP、域名和主机名都是动态分配的,尤其是在容器宕掉重启后,它的IP、域名和主机名很可能会改变。在这种动态的集群里,怎么才能让容器找到自己的兄弟呢?

思路是利用Docker的基于DNS的服务发现机制。在Docker Swarm里,每个服务都带有一个服务发现用的域名,它是tasks.<服务名>,在我们的这套配置里,它是tasks.chitchat_app。如果你在任意一个replica容器里运行nslookup tasks.chitchat_app,你会看到所有replica的IP地址。有了IP地址,接下来只要知道节点的基本名称(节点名称@前面的部分)就行了。这个名称很容易找,因为Elixir的release启动时,环境变量$RELEASE_NAME已经设好了这个名称。

看起来不错,先试一下。让我们先登上1号容器(把那个xxx换成实际值,其实只需要敲Tab就行了):

$ docker exec -it chitchat_app.1.xxx sh

获得其他容器的IP地址:

$ nslookup tasks.chitchat_app

然后attach到正在运行的chitchat进程,并尝试连接其他节点(假定它的IP是10.0.0.3):

$ ./bin/chitchat remote
iex> Node.connect(:"chitchat@10.0.0.3")

你会发现连不上。问题在哪儿?看看当前节点的名称是啥:

iex> Node.self()
:"nonode@nohost"

问题就在这儿。我们的节点没有名称!为了让每个节点有自己的名称,我们需要修改rel/env.sh.eex。

修改rel/env.sh.eex

放开下面两行:

export RELEASE_DISTRIBUTION=name
export RELEASE_NODE="<%= @release.name %>@127.0.0.1"

这个文件用于生成env.sh,而env.sh会在每次应用启动的时候运行,用来设置环境变量。

还有一个问题,怎么把127.0.0.1替换成真正的容器的IP?如果你在某个容器里运行hostname -i,你会得到当前的IP(比较有意思的是,如果你在自己的PC上运行这句命令,你只能拿到127.0.1.1)。所以我们只要把RELEASE_NODE那一行改成

export RELEASE_NODE="<%= @release.name %>@$(hostname -i)"

就一切OK了。顺带一提,rel/env.bat.eex可以不改,因为我们的容器跑的不是Windows而是Alpine Linux。

重新部署一下,再尝试一下连接其他节点,可以看到这次就能连上了。

下一个问题就是怎么让它自动连,而且周期性地反复连。这里我用了一个第三方库Peerage。

集成Peerage

安装方式请自行看官网。我只将我的配置贴出来:

# config/prod.exs
config :peerage, via: Peerage.Via.Dns,
  dns_name: "tasks.chitchat_app",
  app_name: {:system, "RELEASE_NAME"}

这里的dns_name就是Peerage去访问的DNS域名。而app_name则是节点名称@前面的部分。{:system, "RELEASE_NAME"} 告诉Peerage这个名称要去环境变量$RELEASE_NAME里找。Peerage会周期性地访问DNS获取IP,并在每个IP前面加上@,然后尝试连接这些节点。

重新部署一下,然后在某个replica上运行

$ ./bin/chitchat rpc "IO.inspect Node.list"

你会看到其他节点的名称,这表明所有节点都已连上了。

你还可以尝试扩张/缩水当前的服务(参考docker service scale),杀掉某个容器(docker kill)等操作,看看行为是否和预期一样。

到此,关于“Docker Swarm上写一个聊天室应用chitchat”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注创新互联网站,小编会继续努力为大家带来更多实用的文章!


文章标题:DockerSwarm上写一个聊天室应用chitchat
文章路径:http://ybzwz.com/article/peiccj.html