背景

假设一个名为ws的无状态服务,在业务代码中,服务需要根据hostname解析本机ip,并绑定端口启动监听进程, 例如:

import socket

def get_hostname():
    with open("/etc/hostname") as f:
        return f.read().strip()

ip = socket.gethostbyname(get_hostname())

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((ip, 9999))
server.listen(10)

在上面的代码中, 只在‘ip’这一个地址上进行监听。 开启监听后,会将 <主机名>.mgmt.pix.yun.com:9999注册到zk, 使其他服务从zk中取得, 作为目标进行访问。容器化部署该服务需要满足:

  1. socket.gethostbyname能根据容器中的hostname获得容器正确的ip地址
  2. 集群中的其他服务能通过<容器中的主机名>.mgmt.pix.yun.com解析到ws服务的ip地址(在解析外部域名一文中已解决了能将mgmt.pix.yun.com转换为default.svc.cluster.local的问题, 因此该条件等同于集群中的其他服务能通过<容器中的主机名>。default.svc.cluster.local解析到ws服务的ip地址)

分析与方案

按照通常的思路,一个无状态的服务,用kubernetes来部署的话,只需要使用svc + deployment的方式便足够, 但是,却不能满足上面描述的需求, 例如一个pod的名称为‘ws-777779bd59-ftn4j’, 则其注册到zk中的信息为:

ws-777779bd59-ftn4j.mgmt.pix.yun.com:9999

kubernetes集群无法通过该域名解析到ip地址,根据参考文档的介绍, 可以使用headless svc + subdomain的方式,使deployment中的pod也能像statefulset的pod一样,通过类似<pod名>.<svc名>.default.svc.cluster.local的域名被解析

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ws
  labels:
    app: w
spec:
  replicas: 2
  selector:
    matchLabels:
      app: ws
  template:
    metadata:
      labels:
        app: ws
    spec:
      subdomain: ws
      containers:
      - name: ws
        image: webservice
        imagePullPolicy: Always
        ports:
            - containerPort: 8880
---
apiVersion: v1
kind: Service
metadata:
  name: ws
spec:
  clusterIP: None
  selector:
    app: ws
  ports:
  - name: nginx
    protocol: TCP
    port: 8880
    targetPort: 8880

如上进行设置, 则在ws的pod内部就能通过<主机名>.ws.default.svc.cluster.local 解析到各自的ip地址了

# 容器内部查看本机的ip地址
root@ws-777779bd59-ftn4j:/$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    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
22: eth0@if23: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1376 qdisc noqueue state UP group default 
    link/ether 6a:7d:2d:d4:2e:ef brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.36.0.1/12 brd 10.47.255.255 scope global eth0
       valid_lft forever preferred_lft forever

# 用python检验域名解析是否能获取到本机的ip
root@ws-777779bd59-ftn4j:/$ python
Python 2.7.12 (default, Nov 12 2018, 14:36:49) 
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import socket
>>> socket.gethostbyname("ws-777779bd59-ftn4j.ws.default.svc.cluster.local")
'10.36.0.1'
>>> socket.gethostbyname("ws-777779bd59-ftn4j.ws.mgmt.pix.yun.com")
'10.36.0.1'

在deployment的另一个容器内查看:

root@ws-777779bd59-nnppt:/$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    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
114: eth0@if115: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1376 qdisc noqueue state UP group default 
    link/ether 2e:23:56:a4:2f:e9 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.32.0.5/12 brd 10.47.255.255 scope global eth0
       valid_lft forever preferred_lft forever

# 用python检验域名解析是否能获取到本机的ip
root@ws-777779bd59-nnppt:/$ python
Python 2.7.12 (default, Nov 12 2018, 14:36:49) 
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import socket
>>> socket.gethostbyname("ws-777779bd59-nnppt.ws.default.svc.cluster.local")
'10.36.0.5'
>>> socket.gethostbyname("ws-777779bd59-nnppt.ws.mgmt.pix.yun.com")
'10.36.0.5'

因此, 只需要把/etc/hostname中的主机名加上后缀‘.ws’即可, 可以在容器的启动脚本内添加

echo $HOSTNAME.ws > /etc/hostname

 

Logo

开源、云原生的融合云平台

更多推荐