Skip to content

Instantly share code, notes, and snippets.

@inductor
Last active January 20, 2021 11:55
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save inductor/de2142dd4274f898cb9ef5a1cbade419 to your computer and use it in GitHub Desktop.
Save inductor/de2142dd4274f898cb9ef5a1cbade419 to your computer and use it in GitHub Desktop.
kubeletソースコードリーディングメモ.md

目的

KubernetesでResource Limit/Requestがどう動くのかが気になって、いろいろ考えたんだけど、

Pod spec -> kubelet -> CRI Runtime -> OCI runtime -> cgroup の順番でどういうデータの受け渡しがされているかというところを知りたくて調査を始めた

とりあえずkubeletから。バージョンは1.19ベースで。

https://github.com/kubernetes/kubernetes/blob/release-1.19/pkg/kubelet/kuberuntime/kuberuntime_manager.go#L661

func (m *kubeGenericRuntimeManager) SyncPod(pod *v1.Pod, podStatus *kubecontainer.PodStatus, pullSecrets []v1.Secret, backOff *flowcontrol.Backoff) (result kubecontainer.PodSyncResult) {

SyncPodという関数でPod specをsyncしてきているのでここからスタートする。

今回作成だけ考えるので諸々すっ飛ばしてcontainerStartSpecまで飛ぶ

	// Step 7: start containers in podContainerChanges.ContainersToStart.
	for _, idx := range podContainerChanges.ContainersToStart {
		start("container", containerStartSpec(&pod.Spec.Containers[idx]))
	}

startのhelperがすぐ上にあった。

	start := func(typeName string, spec *startSpec) error {
		startContainerResult := kubecontainer.NewSyncResult(kubecontainer.StartContainer, spec.container.Name)
		result.AddSyncResult(startContainerResult)

		isInBackOff, msg, err := m.doBackOff(pod, spec.container, podStatus, backOff)
		if isInBackOff {
			startContainerResult.Fail(err, msg)
			klog.V(4).Infof("Backing Off restarting %v %+v in pod %v", typeName, spec.container, format.Pod(pod))
			return err
		}

		klog.V(4).Infof("Creating %v %+v in pod %v", typeName, spec.container, format.Pod(pod))
		// NOTE (aramase) podIPs are populated for single stack and dual stack clusters. Send only podIPs.
		if msg, err := m.startContainer(podSandboxID, podSandboxConfig, spec, pod, podStatus, pullSecrets, podIP, podIPs); err != nil {
			startContainerResult.Fail(err, msg)
			// known errors that are logged in other places are logged at higher levels here to avoid
			// repetitive log spam
			switch {
			case err == images.ErrImagePullBackOff:
				klog.V(3).Infof("%v %+v start failed in pod %v: %v: %s", typeName, spec.container, format.Pod(pod), err, msg)
			default:
				utilruntime.HandleError(fmt.Errorf("%v %+v start failed in pod %v: %v: %s", typeName, spec.container, format.Pod(pod), err, msg))
			}
			return err
		}

		return nil
	}

startContainerってやつを呼んでいるのでkuberuntime_containerへ飛ぶ。下記step2で作成処理を呼んでいる。

https://github.com/kubernetes/kubernetes/blob/release-1.19/pkg/kubelet/kuberuntime/kuberuntime_container.go#L134

func (m *kubeGenericRuntimeManager) startContainer(podSandboxID string, podSandboxConfig *runtimeapi.PodSandboxConfig, spec *startSpec, pod *v1.Pod, podStatus *kubecontainer.PodStatus, pullSecrets []v1.Secret, podIP string, podIPs []string) (string, error) {
...
	// Step 2: create the container.
	// For a new container, the RestartCount should be 0
	restartCount := 0
	containerStatus := podStatus.FindContainerStatusByName(container.Name)
	if containerStatus != nil {
		restartCount = containerStatus.RestartCount + 1
	}

	target, err := spec.getTargetID(podStatus)
	if err != nil {
		s, _ := grpcstatus.FromError(err)
		m.recordContainerEvent(pod, container, "", v1.EventTypeWarning, events.FailedToCreateContainer, "Error: %v", s.Message())
		return s.Message(), ErrCreateContainerConfig
	}

	containerConfig, cleanupAction, err := m.generateContainerConfig(container, pod, restartCount, podIP, imageRef, podIPs, target)
	if cleanupAction != nil {
		defer cleanupAction()
	}

generateContainerConfigでConfigを設定しているみたいだ。下記の通り、CPU/Memoryのrequest/limitにたどり着いた。

https://github.com/kubernetes/kubernetes/blob/release-1.19/pkg/kubelet/kuberuntime/kuberuntime_container_linux.go#L55-L58

	var cpuShares int64
	cpuRequest := container.Resources.Requests.Cpu()
	cpuLimit := container.Resources.Limits.Cpu()
	memoryLimit := container.Resources.Limits.Memory().Value()
	oomScoreAdj := int64(qos.GetContainerOOMScoreAdjust(pod, container,
		int64(m.machineInfo.MemoryCapacity)))
@inductor
Copy link
Author

inductor commented Dec 8, 2020

CPUの要求は人間がわかりやすい値をhelpers_linuxを使ってcgroup向けの値に書き換えているっぽい。

	if m.cpuCFSQuota {
		// if cpuLimit.Amount is nil, then the appropriate default value is returned
		// to allow full usage of cpu resource.
		cpuPeriod := int64(quotaPeriod)
		if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.CPUCFSQuotaPeriod) {
			cpuPeriod = int64(m.cpuCFSQuotaPeriod.Duration / time.Microsecond)
		}
		cpuQuota := milliCPUToQuota(cpuLimit.MilliValue(), cpuPeriod)
		lc.Resources.CpuQuota = cpuQuota
		lc.Resources.CpuPeriod = cpuPeriod
	}

https://github.com/kubernetes/kubernetes/blob/release-1.19/pkg/kubelet/kuberuntime/helpers_linux.go

この思想自体は https://github.com/google/lmctfy から引き継がれたもので、今はDockerを始めとしたランタイムに取り込まれている思想(libcontainerdに取り込まれた、とこのリポジトリに書いてある)

@inductor
Copy link
Author

inductor commented Dec 8, 2020

なるほど、kubeletがCRIに値を渡す時点で既にCPUはmilisecondな値になっているのか。

@inductor
Copy link
Author

inductor commented Dec 8, 2020

https://ja.wikipedia.org/wiki/Completely_Fair_Scheduler

CFSQuota is 何と思っていたらLinuxのタスクスケジューラーであった。

@inductor
Copy link
Author

inductor commented Dec 8, 2020

https://github.com/kubernetes/kubernetes/blob/release-1.19/staging/src/k8s.io/cri-api/pkg/apis/services.go#L35

ここにgRPCの定義があるのでここからCRIランタイムに情報が渡りそう。

@inductor
Copy link
Author

inductor commented Dec 8, 2020

containerdに待ち受けてるCreateContainerの関数がいた。

https://github.com/containerd/containerd/blob/master/pkg/cri/server/container_create.go#L50

@inductor
Copy link
Author

inductor commented Dec 8, 2020

@inductor
Copy link
Author

inductor commented Dec 8, 2020

OCIランタイムをどこで呼んでるのかがわからん!

@inductor
Copy link
Author

inductor commented Dec 8, 2020

すげー、何でも質問できちゃうぜSlack
image

@inductor
Copy link
Author

inductor commented Dec 8, 2020

つーわけでcontainerdにおける"container"はただのメタデータにすぎず、実際のコンテナはTaskという単位で作られるらしい。

https://github.com/containerd/containerd/blob/master/container.go#L298 ここで呼ばれたc.client.TaskService().Create(ctx, request)からgRPCのCreate()命令にたどり着いた。

@inductor
Copy link
Author

inductor commented Dec 8, 2020

んでもってruncまで来るとCreateがここで行われているのがわかる。
https://github.com/opencontainers/runc/blob/master/libcontainer/factory_linux.go#L239

手法はよくわからないけど、この構造体ポインタが呼ばれたタイミングでコンテナを作ってるように見える

https://github.com/opencontainers/runc/blob/master/libcontainer/factory_linux.go#L264

@inductor
Copy link
Author

inductor commented Dec 8, 2020

「cgroupはファイルシステムの直接操作や、これをラップした以下のコマンドで操作します。Namespaceと異なりシステムコールでは操作しません。」

ほほー。面白い

ref. https://eh-career.com/engineerhub/entry/2019/02/05/103000#cgroup%E3%81%A7%E3%83%AA%E3%82%BD%E3%83%BC%E3%82%B9%E3%82%92%E5%88%B6%E9%99%90%E3%81%99%E3%82%8B

@inductor
Copy link
Author

inductor commented Dec 8, 2020

だいぶ話がそれてしまったが、runcの中でcgroupsのリソースに関するオプションを見つけた。CpuQuotaがいるので、kubeletからもらった値がそのまま横流しにされてる感じっぽい。
https://github.com/containerd/containerd/blob/master/pkg/cri/server/container_create_linux.go#L238

処理の流れはどうかな・・・

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment