背景
在推动业务上容器过程中,存在业务方框架(如Java的dubbo)对ip依赖较重,但框架改造周期较长的问题。为了解决这个问题,运维侧从网络层面固定容器IP的方式着手,引入了腾讯开源的Galaxy插件。这里对此插件的安装部署进行说明。
Galaxy架构概览

Galaxy网络方案主要包括两个模块:
- galaxy:以daemonset形式存在每个k8s集群的节点上,它通过判断pod annotation信息,来设定pod网络是用固定ip还是非固定ip
- galaxy-ipam:根据pod的生命周期,完成pod ip的分配、释放、已分配ip信息的记录等功能
配置部署
预备环境
已有K8S集群环境
组件名 | 版本 |
kube各组件 | 1.16.4 |
docker | 19.03.5 |
Galaxy方案的落地,需要结合cni插件一起来完成。Galaxy支持多种cni插件,包括flannel、SRIOV、vlan等,这里我们选择vlan的方式。
因此,每个k8s集群节点上需要安装kubernete-cni
yum install -y kubernetes-cni注意:
执行以上操作之后,由于kubernetes-cni依赖于kubelet,导致其自动安装了最新版本的kubelet,将我们原本1.16.4的kubelet覆盖,所以需要做以下回退操作:rpm -e kubelet --nodeps && yum install -y kubelet-1.16.4
镜像准备
部署过程中,Galaxy网络方案的两个模块都是以容器方式运行的,因此我们先定制镜像,推送到我们的私有容器仓库。我们在部署使用的时候,当时galaxy最新版是v1.0.2,这里也以此版本来进行部署说明。
1. 下载镜像
我们需要的是:tkestack/galaxy:v1.0.2和tkestack/galaxy-ipam:v1.0.2两个镜像。
由于国内下载速度感人,大家可以想办法用其他代理方式进行下载,这里就不多说了。
2. 定制镜像
下载的镜像时区并不是北京时间的,可以采用两种方式解决:
- 方法一:每次使用镜像时,在yaml加上挂载宿主机/etc/localtime的配置
- 方法二:直接修改原镜像中的/etc/localtime为/usr/share/zoneinfo/Asia/Shanghai,一劳永逸
个人倾向方法二,这里以galaxy:v1.0.2镜像制作为例进行说明(假设,私用仓库地址为:myhub.example.com/op-base)。
修改时区的Dockerfile如下:
FROM tkestack/galaxy:v1.0.2
RUN rm -f /etc/localtime \
&& ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime在Dockerfile文件所在目录,执行build操作:
docker build -t myhub.example.com/op-base/galaxy:v1.0.2 .将镜像推送到我们的私有Harbor仓库(推送需要账号权限,需先docker login):
docker push myhub.example.com/op-base/galaxy:v1.0.2配置部署galaxy模块
在Galaxy项目https:///tkestack/galaxy/tree/master/yaml路径下有我们需要的yaml配置文件:

将其下载到有kubectl的节点,但是这些yaml有些地方需要修改。
1 运行galaxy的daemonset
配置部署galaxy模块,使用的是galaxy.yaml,有两处需要修改
(1)修改镜像和启动参数
......
imagePullSecrets:
- name: myhub
containers:
- image: myhub.example.com/op-base/galaxy:v1.0.2
command: ["/bin/sh"]
# qcloud galaxy should run with --route-eni
# args: ["-c", "cp -p /etc/galaxy/cni/00-galaxy.conf /etc/cni/net.d/; cp -p /opt/cni/galaxy/bin/galaxy-sdn /opt/cni/galaxy/bin/loopback /opt/cni/bin/; /usr/bin/galaxy --logtostderr=true --v=3 --route-eni"]
# private-cloud should run without --route-eni
args: ["-c", "cp -p /etc/galaxy/cni/00-galaxy.conf /etc/cni/net.d/; cp -p /opt/cni/galaxy/bin/galaxy-sdn /opt/cni/galaxy/bin/loopback /opt/cni/bin/; /usr/bin/galaxy --logtostderr=true --v=3"]
......说明:
- 修改image为我们自定义的镜像
myhub.example.com/op-base/galaxy:v1.0.2 - 指定从私有仓库拉取镜像所需的secrets:
imagePullSecrets配置为name: myhub(这里根据自己实际情况而定) - 部署环境为自建机房,不是腾讯云,所以这里启动参数使用不带
--route-eni
(2)修改galaxy的网络配置
我们的诉求是:在一个K8S集群上,pod需要既能配置不使用固定ip,也可以配置使用固定ip。
即,在加上指定annotation参数时才使用固定ip方式配置pod网络,默认情况下,不用固定ip。
因此,我们采用bridge+vlan的两种网络方式:
默认情况下,走bridge网络,不会固定网络ip;
当识别带有
......
apiVersion: v1
kind: ConfigMap
metadata:
name: galaxy-etc
namespace: kube-system
data:
# update network card name in "galaxy-k8s-vlan" and "galaxy-k8s-sriov" if necessary
# update vf_num in "galaxy-k8s-sriov" according to demand
galaxy.json: |
{
"NetworkConf":[
{"name":"tke-route-eni","type":"tke-route-eni","eni":"eth1","routeTable":1},
{"name":"galaxy-flannel","type":"galaxy-flannel", "delegate":{"type":"galaxy-veth"},"subnetFile":"/run/flannel/subnet.env"},
{"name":"galaxy-k8s-vlan","type":"galaxy-k8s-vlan", "device":"eth0"},
{"name":"galaxy-k8s-sriov","type": "galaxy-k8s-sriov", "device": "eth1", "vf_num": 10}
],
"DefaultNetworks": ["my-bridge"],
"ENIIPNetwork": "galaxy-k8s-vlan"
}
......说明:
-
DefaultNetworks指定为使用bridge方式,这里取名为my-bridge(这个名字需要跟服务器节点上cni配置中的bridge名一致) -
device指定服务器的网卡名为eth0(网卡名根据实际情况来修改)
除了上面两处修改外,别忘了添加cni的bridge配置
# cat /etc/cni/net.d/10-my-bridge.conf
{
"cniVersion": "0.2.0",
"name": "my-bridge",
"type": "bridge",
"bridge": "cbr0",
"addIf": "eth0",
"isGateway": true,
"ipMasq": false,
"ipam": {
"type": "host-local",
"ranges": [
[{
"subnet": "10.20.30.0/26"
}]
],
"routes": [
{ "dst": "0.0.0.0/0" }
]
}
}最后,执行如下命令就可以将galaxy的daemonset运行起来:
kubectl apply -f galaxy.yaml运行成功后,在集群节点上会发现:
-
/opt/cni/bin/目录下多了galaxy-sdn和loopback两个二进制文件; -
/etc/cni/net.d/目录下生成了一个00-galaxy.conf配置文件,内容如下:
{
"type": "galaxy-sdn",
"capabilities": {"portMappings": true},
"cniVersion": "0.2.0"
}注意:之所以将bridge配置文件10-my-bridge.conf取名为10-前缀,是为了让其排在00-前缀的00-galaxy.conf文件后面被加载,不然会出现奇怪的问题。
2 配置kubelet支持cni网络插件
在kubelet的启动脚本里加上--network-plugin=cni --cni-bin-dir=/opt/cni/bin/参数,然后重启kubelet。
kubelet的完整启动脚本内容类似如下:
# /usr/lib/systemd/system/kubelet.service
[Unit]
Description=kubelet: The Kubernetes Node Agent
Documentation=https://kubernetes.io/docs/
[Service]
ExecStart=/usr/bin/kubelet \
--eviction-hard=memory.available<1024Mi,nodefs.available<10%,nodefs.inodesFree<5% \
--system-reserved=cpu=1,memory=1G \
--kube-reserved=cpu=1,memory=1G \
--cgroups-per-qos=true \
--address={{your_node_ip}} \
--hostname-override={{your_node_ip}} \
--cgroup-driver=systemd \
--pod-infra-container-image=myhub.example.com/kubernetes/pause-amd64:3.1 \
--experimental-bootstrap-kubeconfig=/etc/kubernetes/bootstrap.conf \
--kubeconfig=/etc/kubernetes/kubelet.conf \
--cert-dir=/etc/kubernetes/pki \
--cluster-dns={{your_dns_ip}} \
--cluster-domain={{your_domain_postfix}}. \
--network-plugin=cni \
--cni-bin-dir=/opt/cni/bin/ \
--hairpin-mode=promiscuous-bridge \
--fail-swap-on=false \
--feature-gates=RotateKubeletServerCertificate=true,RotateKubeletClientCertificate=true \
--rotate-certificates \
--serialize-image-pulls=false \
--max-pods=60 \
--logtostderr=true \
--v=2
Restart=always
StartLimitInterval=0
RestartSec=5
[Install]
WantedBy=multi-user.target配置部署galaxy-ipam模块
1. 配置floatingip-config
假设:
k8s节点所属ip网段为:10.10.100.0/24
容器可用ip范围为:10.11.128.1~10.11.135.253
容器ip所属网段为:10.11.128.0/21
容器的网关ip为:10.11.135.254
vlan的id为:1200
注:这些网络信息,实际通常需要网络组同事帮忙分配确定
以ConfigMap的方式配置,这里创建floatingip-config.yaml配置文件,内容如下:
kind: ConfigMap
apiVersion: v1
metadata:
name: floatingip-config
namespace: kube-system
data:
floatingips: '[{"routableSubnet":"10.10.100.0/24","ips":["10.11.128.1~10.11.135.253"],"subnet":"10.11.128.0/21","gateway":"10.11.135.254","vlan":1200}]'说明:
- routableSubnet:指定kubelet节点所在网段
- ips:指定为容器ip分配的地址范围
- subnet:容器ip的网段
- gateway:容器地址分配的网关
- vlan:是用来指定容器ip的vlan id,当容器ip和节点ip不属于同一个vlan时需要配置(id为数值类型,写的时候不要带引号)
floatingip-config.yaml创建完后,执行以下命令生效:
kubectl apply -f floatingip-config.yaml2 修改galaxy-ipam.yaml配置文件
(1)修改镜像
......
imagePullSecrets:
- name: myhub
containers:
- image: myhub.example.com/op-base/galaxy-ipam:v1.0.2
......说明:
- 修改image为自定义镜像
myhub.example.com/op-base/galaxy-ipam:v1.0.2 - 指定从私有仓库拉取镜像所需的secrets:
imagePullSecrets配置为name: myhub(这里根据自己实际情况而定)
(2)修改galaxy-ipam-etc配置
......
apiVersion: v1
kind: ConfigMap
metadata:
name: galaxy-ipam-etc
namespace: kube-system
data:
# delete cloudProviderGrpcAddr if not ENI
galaxy-ipam.json: |
{
"schedule_plugin": {
"storageDriver": "k8s-crd",
"cloudProviderGrpcAddr": "127.0.0.2:80" # 删除此行
}
}
......说明:
- 由于我们是自建IDC环境,删除
"cloudProviderGrpcAddr": "127.0.0.2:80"此行配置
修改完后,执行以下命令生效:
kubectl apply -f galaxy-ipam.yaml3 配置kube-scheduler
(1)首先配置scheduler-policy.yaml:
# make sue --policy-configmap=scheduler-policy of kube-scheduler is set
# note that --policy-config-file and --use-legacy-policy-config is conflict with --policy-configmap
apiVersion: v1
kind: ConfigMap
metadata:
name: scheduler-policy
namespace: kube-system
data:
# set "ignoredByScheduler" to true if not ENI
policy.cfg: |
{
"kind": "Policy",
"apiVersion": "v1",
"extenders": [
{
"urlPrefix": "http://10.20.30.40:9040/v1",
"httpTimeout": 70000000000,
"filterVerb": "filter",
"BindVerb": "bind",
"weight": 1,
"enableHttps": false,
"managedResources": [
{
"name": "/eni-ip",
"ignoredByScheduler": true
}
]
}
]
}说明:
-
urlPrefix这里指定的地址是galaxy-ipam的svc的CLUSTER-IP(可以通过kubectl get svc -n kube-system查看) -
ignoredByScheduler的说明是这样的If you want to limit each node's max Float IPs, please set ignoredByScheduler to false, then the Float IP resource will be judge by scheduler's PodFitsResource algorithm.。这里没有太理解它的意思,由于我们是自建IDC环境,按照注释提示,应该设为true
(2)配置kube-scheduler的启动脚本
在kube-scheduler的启动脚本里添加参数:--policy-configmap=scheduler-policy,然后重启kube-scheduler
注意:要确保kube-scheduler有访问configmap的权限,否则会有类似如下的报错:

为system:kube-scheduler添加configmaps权限最直接的方法就是执行如下命令进入编辑:
kubectl edit clusterrole system:kube-scheduler -o yaml然后添加:
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
- list
- watch
- update
- create
- patch之后再查看已经有了configmaps权限:
kubectl describe clusterrole system:kube-scheduler
这时再执行systemctl restart kube-scheduler重启即可。
如何让pod配置使用固定ip
当galaxy和galaxy-ipam模块按上述步骤配置完成后,配置一个示例验证一下固定ip的功能是否实现
创建一个test-busybox.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-busybox
spec:
replicas: 3
selector:
matchLabels:
name: test-busybox
template:
metadata:
labels:
tag: lxcfs
name: test-busybox
annotations:
/networks: "galaxy-k8s-vlan"
k8s.v1.cni.galaxy.io/release-policy: "never"
spec:
tolerations:
- operator: "Exists"
containers:
- name: test-busybox
image: busybox
command:
- sleep
- "3600"
resources:
requests:
cpu: "0.1"
memory: "32Mi"
/eni-ip: "1"
limits:
cpu: "0.1"
memory: "32Mi"
/eni-ip: "1"说明:
- annotations配置:
-
/networks: "galaxy-k8s-vlan":指明使用的网络类型为galaxy-k8s-vlan k8s.v1.cni.galaxy.io/release-policy: "never":指明ip的释放策略,有never和immutable两种
never: Never Release IP even if the Deployment or Statefulset is deleted. Submitting a same name Deployment or Statefulset will reuse previous reserved IPs.
immutable: Release IP Only when deleting or scaling down Deployment or Statefulset. If POD float onto a new node in case of original Node became NotReady, it will get the previous IP.注: 在跟galaxy开发者交流时,他建议使用
never参数,immutable可能在后面的迭代中被废弃掉。
- resources配置:
requests和limits属性都要添加/eni-ip: "1"
不配置的话,会报如下错误:
fail to establish network map[]:neither ipInfo from cni args nor ipam type from netconf
通过删除一个正在Running的pod,然后会发现再被创建出来的pod使用的ip和被删除时的ip一样,这便达到了我们固定ip的目的。
v1.0.2版本galaxy存在的问题
经过我们实际使用测试,发现v1.0.2版本存在两个问题:
- 多vlan支持: 当有多个vlan时,一个vlan中的ip分配完之后,不会自动分配下一个vlan的ip,而是pending
- svc问题:启用了固定ip的pod访问集群任意的svc地址都不通
对于第一个问题,我们使用一个vlan,然后在交换机层面用vxlan来隔离来避开了。
对于第二个问题,由于我们业务应用暂时没有访问svc的场景,所以也暂时不影响使用。
目前,galaxy已更新到了v1.0.3版本,新版本中已经解决了上述两个问题。
















