背景
在推动业务上容器过程中,存在业务方框架(如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://github.com/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.yaml
2 修改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.yaml
3 配置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": "tke.cloud.tencent.com/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:
k8s.v1.cni.cncf.io/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"
tke.cloud.tencent.com/eni-ip: "1"
limits:
cpu: "0.1"
memory: "32Mi"
tke.cloud.tencent.com/eni-ip: "1"
说明:
-
annotations配置:
k8s.v1.cni.cncf.io/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
属性都要添加tke.cloud.tencent.com/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版本,新版本中已经解决了上述两个问题。