首页 > 其他分享 >k8s中cluster内如何访问service

k8s中cluster内如何访问service

时间:2025-01-03 20:55:05浏览次数:8  
标签:service kubernetes cluster API DNS pod k8s

intro

由于k8s中的部署是声明式的:为了满足部署的需求,pod可以动态的销毁/创建/迁移等。这种飘忽不定的生命期就导致了具体提供服务的pod的IP地址(cluster ip)随之经常变化。

为了解决这个问题,k8s使用的是和DNS类似的思路,通过内部DNS服务来解决这个问题:尽管提供服务器的pod ip会经常变化,但是service的名字却不会变化。当需要访问服务的时候,通过service名字而不是具体的IP地址来访问即可。

那么问题马上就又来了:dns服务器如何知道这些服务具体由哪些(动态)IP提供呢?

DNS如何感知服务

这一点其实在k8s的文档中已经明确说明(无怪乎k8s的manual是最好的k8s学习文档):

A cluster-aware DNS server, such as CoreDNS, watches the Kubernetes API for new Services and creates a set of DNS records for each one. If DNS has been enabled throughout your cluster then all Pods should automatically be able to resolve Services by their DNS name.

也就是说:类似于CoreDNS这种DNS服务器,会监视 Kubernetes API中的新服务器,并为每个服务创建新的(DNS)记录。

CoreDNS

CoreDNS工程的kubernetes插件说明了配置中支持在配置文件中配置endpoint来制定k8s API的地址。

kubernetes [ZONES...] {
    endpoint URL

并进而对该配置型做了说明:

  • endpoint specifies the URL for a remote k8s API endpoint. If omitted, it will connect to k8s in-cluster using the cluster service account.

但是,在验证一个实际运行的CoreDNS容器的配置时,可以发现其中并没有通过这个配置来制定API的地址

tsecer@harry: docker cp -L bf127988ff25:/etc/coredns/Corefile  ./aaa
Successfully copied 2.05kB to /aaa
tsecer@harry: cat ./aaa
.:53 {
    errors
    health {
       lameduck 5s
    }
    ready
    kubernetes cluster.local in-addr.arpa ip6.arpa {
       pods insecure
       fallthrough in-addr.arpa ip6.arpa
       ttl 30
    }
    prometheus :9153
    forward . /etc/resolv.conf {
       max_concurrent 1000
    }
    cache 30
    loop
    reload
    loadbalance
}
tsecer@harry: 

环境变量

好在CoreDNS工程是开源的,简单搜索下工程中的字符串,可以大致定位到对于这部分功能的处理逻辑:

///@file: coredns\plugin\kubernetes\kubernetes.go
func (k *Kubernetes) getClientConfig() (*rest.Config, error) {
	if k.ClientConfig != nil {
		return k.ClientConfig.ClientConfig()
	}
	loadingRules := &clientcmd.ClientConfigLoadingRules{}
	overrides := &clientcmd.ConfigOverrides{}
	clusterinfo := clientcmdapi.Cluster{}
	authinfo := clientcmdapi.AuthInfo{}

	// Connect to API from in cluster
	if len(k.APIServerList) == 0 {
		cc, err := rest.InClusterConfig()
		if err != nil {
			return nil, err
		}
		cc.ContentType = "application/vnd.kubernetes.protobuf"
		cc.UserAgent = fmt.Sprintf("%s/%s git_commit:%s (%s/%s/%s)", coremain.CoreName, coremain.CoreVersion, coremain.GitCommit, runtime.GOOS, runtime.GOARCH, runtime.Version())
		return cc, err
	}

当配置文件中的endpoint没有对应配置时,执行的InClusterConfig函数:

///@file: k8s.io\client-go@v0.31.2\rest\config.go
// InClusterConfig returns a config object which uses the service account
// kubernetes gives to pods. It's intended for clients that expect to be
// running inside a pod running on kubernetes. It will return ErrNotInCluster
// if called from a process not running in a kubernetes environment.
func InClusterConfig() (*Config, error) {
	const (
		tokenFile  = "/var/run/secrets/kubernetes.io/serviceaccount/token"
		rootCAFile = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
	)
	host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT")
	if len(host) == 0 || len(port) == 0 {
		return nil, ErrNotInCluster
	}

	token, err := os.ReadFile(tokenFile)
	if err != nil {
		return nil, err
	}

	tlsClientConfig := TLSClientConfig{}

	if _, err := certutil.NewPool(rootCAFile); err != nil {
		klog.Errorf("Expected to load root CA config from %s, but got err: %v", rootCAFile, err)
	} else {
		tlsClientConfig.CAFile = rootCAFile
	}

	return &Config{
		// TODO: switch to using cluster DNS.
		Host:            "https://" + net.JoinHostPort(host, port),
		TLSClientConfig: tlsClientConfig,
		BearerToken:     string(token),
		BearerTokenFile: tokenFile,
	}, nil
}

这个函数也不用细看,其中赫然以字符串的形式描述了两个关键的环境变量:KUBERNETES_SERVICE_HOST和KUBERNETES_SERVICE_PORT;并且函数开始的注释也描述了逻辑的主题功能:当一个在k8s中运行的pod希望获得k8s给它(调用该函数的pod)服务账户(service account)。

这两个环境变量在k8s的官方文档中也有明确描述:

While running in a Pod, your container can create an HTTPS URL for the Kubernetes API server by fetching the KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT_HTTPS environment variables. The API server's in-cluster address is also published to a Service named kubernetes in the default namespace so that pods may reference kubernetes.default.svc as a DNS name for the local API server.

client pod

k8s文档说明了:当配置了DNS服务之后,就可以像访问域名一样来通过名字来使用服务:

For example, if you have a Service called my-service in a Kubernetes namespace my-ns, the control plane and the DNS Service acting together create a DNS record for my-service.my-ns. Pods in the my-ns namespace should be able to find the service by doing a name lookup for my-service (my-service.my-ns would also work).

这意味着要模拟一个真实的DNS查询系统,简单来说:就是在每个pod的/etc/resolv.conf中指定这个k8s启动的、感知了系统所有service创建的CoreDNS作为域名查询的一个服务地址。

查看一个pod中的resolv.conf配置

tsecer@harry: docker exec -it 5e70da5dae72 cat /etc/resolv.conf
nameserver 192.168.58.1
search localdomain
options ndots:0 edns0 trust-ad
tsecer@harry: 

我猜测CoreDNS是在192.168.58.1这个地址上侦听服务的,但是因为环境的限制,我没有确认CoreDNS是在这个地址侦听的。通过docker inspect看到输出的IPAddress为空。网上说法是如果这个地址为空,说明使用的是host ip。但是我通过ip addr看到的host ip是 192.168.58.2。

这说明CoreDNS侦听的是192.168.58.2?那这就跟假设的服务访问的是192.168.58.1相矛盾,所以此处存疑(但是感觉这个思路是顺畅的,所以应该是正确的)。可能是我使用的docker driver的minikube功能太受限了。

k8s官方文档看,k8s的dns pod的cluster ip就是其它pod中resolv.conf配置的nameserver地址(10.0.0.10)。

outro

k8s 的API在整个k8s系统中有着至关重要的核心作用,这个组件因为基于etcd实现,配合etcd的watch功能,可以让关心k8s系统事件的组件都可以来侦听系统事件,并进而执行响应的调整/动作。

而k8s启动pod的时候,通过环境变量将API的地址导出给pod,这样需要访问API的pod就可以动态的来watch系统中的动态事件了,neat!

标签:service,kubernetes,cluster,API,DNS,pod,k8s
From: https://www.cnblogs.com/tsecer/p/18650902

相关文章