Intro
v1002 릴리즈 부터 eks ami에서는 network interface를 처리하는 새로운 기능이 [1]에서 추가됐고, 해당 기능에 대해 자세히 살펴보았다.
Deep dive
먼저 해당 기능이 추가된 이유에 대해서는 [1]에서 자세히 소개되어있다. amazon-ec2-net-utils 과 nodeadm사이에 node bootstrap시 구성하는 Network 설정문제간 race condition이 있었다.
the issue can only manifest when networking calls (eg. to
ec2.DescribeInstances) are made beforenodeadmhas a chance to updatesystemd-networkdwith a drop-in that forces the network config to match against primary ENI. [2]
[1]에 도입된 새로운 기능에 대해 자세히 살펴보면 다음과 같다.
먼저 udev 장치관리자를 통해 network interface 가 추가되면 udev-net-manager 서비스를 등록하도록 구성돼있다. [3]
network interface가 등록되면 udev-net-manager가 실행되고, manager(cni or systemd)를 확인한뒤 각 인터페이스에 적절하게 네트워크를 구성한다.
- cloud-init의 종료를 기점으로, 종료전 추가된 interface에는 systemd에 관리되는것으로, 종료 이후에는 CNI에 의해 관리되는 것으로 network device에 마킹한다.
func (b *fsBroker) determineManager(_ string) (string, error) {
// this code checks whether cloud-init has finished booting the node, which
// is indicative of most user-controlled actions being completed. it's not
// perfect but it works under the basic assumptions.
const cloudInitBootResultPath = "/run/cloud-init/result.json"
if _, err := os.Stat(cloudInitBootResultPath); err != nil {
if errors.Is(err, os.ErrNotExist) {
return ManagerSystemd, nil
}
return "", err
}
return ManagerCNI, nil
}- systemd로 관리되는 것이면 CNI에 의해 routing이 구성되지 않도록 자체적으로 새롭게 구성한다.
func (c *netManager) manageLink(ctx context.Context) error {
deviceIndex, err := getDeviceIndex(ctx, c.imds, c.selfMac)
if err != nil {
return err
}
networkCard, err := getNetworkCard(ctx, c.imds, c.selfMac)
if err != nil {
return err
}
const (
// see: https://github.com/amazonlinux/amazon-ec2-net-utils/blob/3261b3b4c8824343706ee54d4a6f5d05cd8a5979/lib/lib.sh#L39
metricBase = 512
// see: https://github.com/amazonlinux/amazon-ec2-net-utils/blob/3261b3b4c8824343706ee54d4a6f5d05cd8a5979/lib/lib.sh#L32
ruleBase = 10000
)
templateVars := networkTemplateVars{
MAC: c.selfMac,
// setup route metics. this provides priority on good interfaces over
// ones that could potentially delay startup.
// see: https://github.com/amazonlinux/amazon-ec2-net-utils/blob/3261b3b4c8824343706ee54d4a6f5d05cd8a5979/lib/lib.sh#L348-L366
Metric: metricBase + 100*networkCard + deviceIndex,
}
// we only need to add routes/rules to interfaces beyond the primary.
// see: https://github.com/amazonlinux/amazon-ec2-net-utils/blob/main/lib/lib.sh#L570-L580
if c.selfMac != c.primaryMac {
// setup table id for use in defining default routes.
// see: https://github.com/amazonlinux/amazon-ec2-net-utils/blob/3261b3b4c8824343706ee54d4a6f5d05cd8a5979/lib/lib.sh#L349
templateVars.TableID = ruleBase + 100*networkCard + deviceIndex
// getting the interface ip allows us to create a policy rule through
// the gateway for the default route. we dont care about the error here,
// because if we get a 404 then we assume this is an ipv6 network then
// we wont setup the ip.
//
// TODO: no support for ipv6 today.
if ipv4s, _ := c.imds.GetProperty(ctx, imds.LocalIPv4s(c.selfMac)); len(ipv4s) > 0 {
// at the time we make this request, we expect no other ip addresses
// besides the one assigned on attachment, but we can still split
// this and take the first item since its not empty.
ipv4 := strings.Split(ipv4s, "\n")[0]
templateVars.InterfaceIP = strings.TrimSpace(ipv4)
}
}
networkConfig, err := renderNetworkTemplate(templateVars)
if err != nil {
return fmt.Errorf("failed to render network template: %w", err)
}
return util.WriteFileWithDir(eksNetworkPath(c.iface), networkConfig, 0644)
}Info
실제로는 systemd로 관리되는 경우 /run/systemd/network/70-eks-{iface}.network” 경로에 네트워크 설정파일을 생성한다. [4]
아래 명령어로 interface가 어떤 manager로 초기화 됐는지 로그를 볼 수 있고, race condition이나 네트워크 구성 트러블슛팅할때 확인할 수 있다.
# journalctl --unit=udev-net-manager@${interface}아직 log collector에 해당 로그가 수집되지 않아서, 애를 먹었었다.. 그래서 PR 올렸더니 lint 수정 후 바로 merge 됐다.
Troubleshooting
여기서 문제가되는 부분은 바로 여기이다.
cloud-init의 종료를 기점으로, 종료전 추가된 interface에는 systemd에 관리되는것으로, 종료 이후에는 CNI에 의해 관리되는 것으로 network device에 마킹한다.
cloud-init이 종료된 이후에 생성된 network interface에 대하여만, systemd에 의해 추가 라우팅을 구성해야하는데, cloud-init이 시작되며 nodeadm이 또한 시작되면 race condition이 발생할 수 있다.
CNI 입장에서야, network interface가 systemd/CNI에 의해 관리되는지 알 바가 아니지만 노드 레벨에서는 CNI가 관리하는 ENI와 systemd가 관리되는 경우 라우팅 규칙이 다를 수 있기 떄문에 다르게 관리 되어야한다.
만일, 아래 순서로 초기화된다면 문제가 된다.
- cloud-init 도중 kubelet이 시작되고, CNI가 올라와 라우팅 규칙 설정
- systemd가 다시 한번 라우팅 규칙 설정하여 CNI 규칙에 덮어씀
- CNI는 정상적으로 라우팅 규칙을 설정한것으로 인지하고, IPAM에 따라 pod에 해당 Netowrk Interface에 IP 할당.
Info
systemd에 의해 관리되는 경우에 대하여 예를들면 노드에
attach-network-interface로 onprem 등 연결하기 위한 추가 ENI를 직접 붙이는 경우가 있겠다.
reference [1] https://github.com/awslabs/amazon-eks-ami/pull/2419