docker swarm 中的 TCP 服务器部署和 docker swarm 负载均衡

问题描述 投票:0回答:1

我试图了解docker swarm如何进行负载平衡以及它如何影响套接字服务器的设计(因为服务器必须接受客户端连接才能获取用于返回服务结果的套接字对象) ,为此我创建了以下 echo 服务器

# server.py
class Server:
    def __init__(self, port=5050, header_size=64, encode_format='utf-8'):
        port = port
        host = "localhost"
        self.addr = (host, port)
        self.header_size = header_size
        self.encode_format = encode_format
        self.disconnect_message = 'DISCONNECT'

        self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server.bind(self.addr)
        self.id = np.random.randint(1024)

    def do_stuff(self, conn, addr):
        msg_length = conn.recv(self.header_size).decode(self.encode_format)
        msg_length = int(msg_length)

        # THIS PART WAS REMOVED TO KEEP THINGS SIMPLE
        # message = conn.recv(msg_length) #.decode(self.encode_format)
        # while len(message) < msg_length:
        #     packet = conn.recv(msg_length-len(message)) #.decode(self.encode_format)
        #     message += packet

        print(f"[MSG] {msg_length} from {addr}")
        self.send(conn, f"{msg_length} response from {self.id}")
        return True

    def send(self, conn, msg):
        message = pickle.dumps(msg)
        msg_length = len(message)
        send_length = str(msg_length).encode(self.encode_format)
        send_length += b' ' * (self.header_size - len(send_length))

        # print("Sending response length")
        conn.send(send_length)
        # print("Sending response data")
        # conn.send(message)

    def handle_client(self, conn, addr):
        print("-------------------------------------")
        print(f"[CLIENT] new client {addr} connected.")
        connected = True
        while connected:
            connected = self.do_stuff(conn, addr)
        print(f"[CLIENT] {addr} has disconnected")
        conn.close()
        print(f"[Active CONNECTIONS] {threading.active_count() -2}")

    def start(self):
        self.server.listen()
        print(f"[LISTENING] Server is listening on {self.addr[0]} port {self.addr[1]}")
        print(f"[LISTENING] Server is listening on {self.addr[0]} port {self.addr[1]}")
        try:
            while True:
                print("Listening . . .")
                conn, addr = self.server.accept()
                thread = threading.Thread(target=self.handle_client, args=(conn, addr))
                thread.start()
                print(f"[Active CONNECTIONS] {threading.active_count() -1}")
                sleep(2)
        except KeyboardInterrupt:
            print("Interrupt signal received from Keyboard")
        self.server.close()
        print(f"[STOP] self.server {self.addr[0]} has stopped listening on port {self.addr[1]}")

if __name__ == "__main__":
    server = Server()
    server.start()

我能够使用以下 Dockerfile 在 docker 容器中运行服务器:

FROM python:3.6.9
COPY App /App
WORKDIR /App
RUN pip3 install -U pip
RUN pip3 install numpy
EXPOSE 5050
ENTRYPOINT [ "python3" ]
CMD [ "server.py" ]

docker运行命令:

sudo docker run -it --rm -p 5050:5050 --name test IMAGE_NAME

客户

# Client
PORT = 5050
HOST = "SERVER_IP" # or the IP of the server if is running on a different machine
ADDR = (HOST, PORT)
HEADER = 64
FORMAT = 'utf-8'
DISCONNECT_MESSAGE = 'DISCONNECT'

def send(sock, msg):
    message = pickle.dumps(msg)
    msg_length = len(message)
    send_length = str(msg_length).encode(FORMAT)
    send_length += b' ' * (HEADER - len(send_length))

    sock.send(send_length)

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(ADDR)
for i in range(200):
    msg = f"Message number {i}"
    send(sock, msg)
    response = receive(sock)
    print(response)
sock.close()

但是当部署在 docker swarm 服务中时,客户端会挂起

sock.connect()
(我的集群中有 2 个工作节点和 1 个管理节点)

服务创建命令:

sudo docker service create --name echo_server -p 5050:5050 --replicas 2 PRIVATE_REGISTRY_IP:PORT/IMAGE_NAME:TAG

我发现的所有 swarm 服务器示例都使用 nginx 映像,我能够将其部署在工作节点上,然后使用管理器节点的 IP 访问它(这就是我试图为 TCP 套接字服务器实现的目标)。

  1. 我错过了什么?我做错了什么?
  2. 我应该在 TCP 套接字服务器的设计中做一些不同的事情吗?
  3. 通过 Swarm 负载均衡器的实现,客户端如何连接到服务器?
  4. 它总是被发送到它第一次连接到的同一个服务器吗?
  5. 每个请求都发送到不同的服务器吗?
  6. 所有服务器是否都有 1 个客户端,即负载均衡器(在这种情况下,无需为每个线程打开一个线程),并且负载均衡器有客户端的信息吗?

解决方案

我能够在堆栈中部署服务器

stack.yml

version: "3"
services:
  echo_server:
    image: REGISTRY_IP/IMAGE_NAME
    ports:
      - "5050:5050"
    deploy:
      replicas: 3

然后运行

sudo docker stack deploy -c /path/to/stack.yaml STACK_NAME

然后客户端就能够通过其中一台工作/管理节点 PC 的主机 IP 连接到服务

python docker sockets docker-swarm
1个回答
1
投票

为此目的,Docker swarm 有两个负载均衡器:

当您从服务发布端口时,会调用入口负载均衡器。该端口是从所有 swarm 节点发布的,因此任何节点都可以用于连接到该服务。 docker 将循环连接到任何可用的(健康的)服务副本。

由于此流量通过桥接接口到达,因此您无法侦听“localhost”,因为这将阻止容器实际侦听桥接连接。

请注意,虽然每个 swarm 节点都会接受来自 localhost:port 的连接来连接到入口网络,但容器不能使用“localhost”来连接到它,因为它指的是容器内的 localhost 接口,而不是主机。从容器内部连接到当前本地主机的正确方法是地址:“docker.host.internal:8080”(需要在需要使用它的每个服务上启用它)。

另一个负载均衡器是网状网络负载均衡器。在堆栈中部署服务时,docker 会修改每个容器中的resolve.conf,以指向 docker 解析器,该解析器将解析容器所连接的每个网络的服务名称。 Docker swarm 还为该服务创建一个虚拟 IP,它是第 3 层负载均衡器,并将其与每个网络上该服务的 dns 名称相关联。

因此,如果您部署了一个名为“echotest”的服务作为名为“test”的堆栈的一部分,并且创建了 2 个副本,那么 docker 将分配以下内容:

IP 10.0.1.5 的 VIP,名称为“echotest”和“test_echotest”。

两个任务,IP 分别为 10.0.1.6 和 10.0.1.7。

附加到“test_default”网络 (10.0.1.0/24) 的任何其他服务都可以使用名称“echotest”和“test_echotest”连接到 vip,而 vip 又会实际连接到其中一个容器。

自己进行负载平衡的服务可以使用特殊名称“tasks.echotest”和“tasks.test_echotest”来获取 dns rr 响应:只是任务 ip 的顺序随机化。 [10.0.1.6,10.0.1.7]

这组命令显示了我的集群上的结果。

$ docker network create --attachable --driver overlay test
$ docker service create --network test --replicas 2 --name test-nginx nginx:latest
$ docker run --rm -it --network test nicolaka/netshoot
$ dig test-nginx +short
10.0.38.2
$ dig tasks.test-nginx +short
10.0.38.3
10.0.38.6
$ exit
$ docker service rm test-nginx
$ docker network rm test
© www.soinside.com 2019 - 2024. All rights reserved.