The kubelet is the primary "node agent" that runs on each node and provides you a lot of configuration options, but sometimes it still not enough, and you still want to run an additional configuration on the node before starting or stopping the container.
For example, when you want to get isolated CPUs, but you do not want to exclude CPUs from the load balancing forever, you can disable CPU load balancing by demand for pinned CPUs via sched domains. Once the container will start, our code will fetch the container pinned CPUs and will disable the CPU load balancing for specific CPUs via sched domain, and once container removed, our code will restore back the CPU load balancing.
For such use cases, the CRI can help, it provides different mechanisms to inject the desired configuration via a custom script once a container starts or stops.
Important to notice that currently, we have two main CRI implementations, containerd the default CRI implementation used by the vanilla Kubernetes and CRI-O used by default under the OpenShift. Depends on the specific implementation, availability and the configuration can be different.
OCI hooks
OCI hooks mechanism defines several entry points to inject your code. To be more specific runtime-spec version 1.0.0 supports prestart
, poststart
, and poststop
entry points.
- CRI-O supports OCI hooks with the runtime-spec version 1.0.0
- containerd does not support hooks, you can monitor issue https://github.com/containerd/cri/issues/405
Pros
- have a number of entrypoints
- have a filtering mechanism that will prevent to run the hook code on every container
- easy to configure
Cons
- not supported by containerd
- impossible to prevent use of hooks via RBAC
Configuration
Because currently, only CRI-O supports it, I will concentrate on the CRI-O configuration.
To enable hooks under the CRI-O you should specify hooks directory under the /etc/crio/crio.conf
and add the hook JSON specification under hooks directory.
NOTE: Default search paths for hooks are /etc/containers/oci/hooks.d/
and /usr/share/containers/oci/hooks.d/
Example:
# cat /etc/crio/crio.conf
...
hooks_dir=[]
...
# cat /etc/containers/oci/hooks.d/test-hook.json
{
"version": "1.0.0",
"hook": {
"path": "/usr/libexec/oci/hooks.d/oci-systemd-hook" // path to the the hook binary
},
"when": {
"commands": [".*/init$" , ".*/systemd$"] // run this hook only when the cmd of the container ends with the init or systemd
},
"stages": ["prestart", "poststop"] // run this hook before the container starts and after the container stops
}
You can get the container state under the hook script from the stdin, you should get the JSON format input that will allow to parse it and fetch all relevant information regarding the container state.
You can check the link for a better explanation about the hook schema - https://www.mankier.com/5/oci-hooks.
Runtime wrapper
The second option is to create a wrapper around runc
runtime handler that used by containerd and CRI-O.
Pros
- provide more flexible approach to run the code on every life phase of the container
Cons
- requires to provide a wrapper around the
runc
- more complex configuration
- impossible to prevent use of
RuntimeClass
via RBAC
Configuration
Under the node, you should specify an additional runtime handler under the CRI implementation configuration.
CRI-O
# cat /etc/crio/crio.conf
...
[crio.runtime.runtimes.wrapper]
runtime_path=/usr/bin/wrapper
...
Containerd
# cat /etc/containerd/config.toml
...
[plugins.cri.containerd.runtimes.wrapper]
runtime_type = "io.containerd.runc.v1"
pod_annotations = ["*"] // run for every pod
container_annotations = ["*"] // run for every container
[plugins.cri.containerd.runtimes.wrapper.options]
BinaryName="/usr/bin/wrapper"
...
The wrapper script runs each time when the CRI implementation calls to the runtime. It is important to say that it can be any executable file, but it should call the runc
binary with all relevant parameters before the exit.
Example:
#!/bin/bash
echo "$@"
exec /usr/bin/runc "$@"
Under the cluster, you should create a new RuntimeClass resource, that will use the new runtime handler.
Example:
apiVersion: node.k8s.io/v1beta1
kind: RuntimeClass
metadata:
name: wrapper # The name the RuntimeClass will be referenced by
handler: wrapper # The name of the corresponding CRI configuration
scheduling:
node-selector: [...] # The node selector defines where will run pods that are using this runtimeclass
In the end you should specify the runtime class under the pod specification.
Example:
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
runtimeClassName: wrapper
...
Now when you will start the pod, the CRI implementation will use our wrapper as a runtime handler and execute our script code.
Conclusion
As you can see we have some good options that allow us to run any additional operating system configurations before the container start or on any other life phase of the container. But you should be aware that currently it impossible to limit usage of hooks or runtime classes via standard Kubernetes mechanisms, so do not overuse them.
저자 소개
채널별 검색
오토메이션
기술, 팀, 인프라를 위한 IT 자동화 최신 동향
인공지능
고객이 어디서나 AI 워크로드를 실행할 수 있도록 지원하는 플랫폼 업데이트
오픈 하이브리드 클라우드
하이브리드 클라우드로 더욱 유연한 미래를 구축하는 방법을 알아보세요
보안
환경과 기술 전반에 걸쳐 리스크를 감소하는 방법에 대한 최신 정보
엣지 컴퓨팅
엣지에서의 운영을 단순화하는 플랫폼 업데이트
인프라
세계적으로 인정받은 기업용 Linux 플랫폼에 대한 최신 정보
애플리케이션
복잡한 애플리케이션에 대한 솔루션 더 보기
오리지널 쇼
엔터프라이즈 기술 분야의 제작자와 리더가 전하는 흥미로운 스토리
제품
- Red Hat Enterprise Linux
- Red Hat OpenShift Enterprise
- Red Hat Ansible Automation Platform
- 클라우드 서비스
- 모든 제품 보기
툴
체험, 구매 & 영업
커뮤니케이션
Red Hat 소개
Red Hat은 Linux, 클라우드, 컨테이너, 쿠버네티스 등을 포함한 글로벌 엔터프라이즈 오픈소스 솔루션 공급업체입니다. Red Hat은 코어 데이터센터에서 네트워크 엣지에 이르기까지 다양한 플랫폼과 환경에서 기업의 업무 편의성을 높여 주는 강화된 기능의 솔루션을 제공합니다.