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