설치 아키텍처
- master 1대
- worker 1대
- haproxy 1대
multi master 구축에 대한 방향성을 위해 haproxy에 master를 연결하여 구성하였으나, 간단한 테스트를 위해 1개의 master를 두고 haproxy와 연결하였다.
사전 작업
hostname 설정 | - 노드별로 고유한 hostname을 가지고 있어야 한다 |
방화벽 해제 or 방화벽 규칙 구성 | - https://v1-27.docs.kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/ - 노드끼리 많은 통신을 통해 쿠버네티스가 정상 동작하기 때문에 적절하게 방화벽을 구성하거나 방화벽을 해제해야 정상적으로 클러스터 구축이 가능하다 |
NTP 서버 동기화 | - 쿠버네티스는 여러 개의 노드가 하나의 클러스터로 구성되는 것이기 때문에 각 노드의 시간을 일관되게 맞추는 것이 중요하다 |
스왑 메모리 해제 | - 스왑 메모리를 사용하면 시스템의 동작이 예측 불가능해질 수 있다 - 예를 들어, 메모리가 부족할 때 스왑이 사용되면 성능이 급격히 저하되거나 시스템이 응답하지 않을 수 있다. 이는 클러스터의 신뢰성을 떨어뜨린다 - 쿠버네티스는 각 컨테이너가 할당된 리소스 내에서 동작하도록 격리하는 것을 목표로 한다. 스왑 메모리를 사용하면 이러한 리소스 격리가 깨질 수 있다 - 쿠버네티스는 노드의 리소스를 효율적으로 관리하고 스케줄링하기 위해 설계되었다. 스왑 메모리가 활성화되면, 쿠버네티스가 노드의 실제 메모리 사용량을 정확하게 파악하는 것이 어려워진다 |
컨테이너 런타임 설정
https://v1-27.docs.kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/
컨테이너 런타임이란 쿠버네티스를 설치할 때 반드시 설치해야하는 것 중 하나이다.
쿠버네티스는 런타임이 나올 때 마다 kubelet을 수정해야 하는 문제를 해결하기 위해 CRI(Container Rumtime Interface)라는 API 규격을 만들게 된다.
컨테이너 런타임이 CRI 스펙만 맞춰서 개발하면 kubelet 코드 변환 없이 새로운 런타임을 사용할 수 있게 된다.
동시에 런타임 자체에 대한 표준도 만들기 위한 움직임이 있었고 그 중심에는 Docker가 있었다. 그래서 생긴 것이 OCI 이다.
docker에서는 docker shim이라는 CRI 인터페이스 구현체를 통해 별도 소통하였으나 docker 진영은 dockershim을 버리고, containerd를 CRI-Containerd와 결합하여 CRI와 OCI 표준을 완벽하게 지원하도록 발전시켰다.
Red Hat / IBM / Intel / SUSE / Hyper ... 등은 연합하여 CRI-O 라는 구현체를 개발했다.
containerd는 OCI 스펙을 준수하며 컨테이너 런타임의 목적에 집중한 컴포넌트이다.
OCI와 CRI를 조금 더 자세히 정리해보자면 다음과 같다.
OCI (Open Container Initiative) | - 커널단과의 규격을 맞추기 위함 (저수준 컨테이너 런타임) - runc : container를 실행/생성 하는 llinux 레벨의 low level 런타임 (Docker진영에서 OCI 스펙을 만들면서 기부한 모듈) - runtime-spec : 실행되는 컨테이너의 구성, 환경, 수명주기에 관한 규칙 - image-spec : 컨테이너 이미지에 대한 규격 - distribution-spec : 콘텐츠의 배포등을 고려한 API 프로토콜 스펙에 관한 규칙 - go-digest : OCI에서 공통으로 사용되는 hash |
CRI (Container Runtime Interface) | - 쿠버네티스와의 규격을 맞추기 위함 (고수준 컨테이너 런타임) - 대표적인 CRI 스펙을 따르는 구현체로는 CRI-O와 containerd가 있다. |
1. containerd 설치
kubernetes에서 지원하는 컨테이너런타임에는 containerd, crio, cri-dockerd 등이 있으나 가장 많이 사용되는 containerd로 설치해보겠다.
containerd 설치 방법은 공식 (1)바이너리 설치 (2) apt-get / dnf 설치 (3)소스코드 직접 빌드 후 설치 등 3가지가 있다.
아래 방법은 공식 바이너리를 다운받아 설치한 방법이다.
바이너리를 통해 설치 시 containerd, runc, cni plugin 3가지를 설치해야 한다. (apt-get 등으로 설치시 runc와 cni plugin을 자동으로 함께 설치)
# 공식 깃허브 홈페이지에서 버전에 맞는 바이너리를 tar 파일 다운로드
$ wget https://github.com/containerd/containerd/releases/download/v1.7.0/containerd-1.7.0-linux-amd64.tar.gz
--2024-06-08 11:45:23-- https://github.com/containerd/containerd/releases/download/v1.7.0/containerd-1.7.0-linux-amd64.tar.gz
Resolving github.com (github.com)... 20.248.137.48
Connecting to github.com (github.com)|20.248.137.48|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://objects.githubusercontent.com/github-production-release-asset-2e65be/46089560/d4d9a85e-64ab-4256-b18d-800f223e97d1?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=releaseassetproduction%2F20240608%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240608T114524Z&X-Amz-Expires=300&X-Amz-Signature=4fc5b909e2dfdec95c403a2e81a239decb94dbc8205511188b3f4008f890bc78&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=46089560&response-content-disposition=attachment%3B%20filename%3Dcontainerd-1.7.0-linux-amd64.tar.gz&response-content-type=application%2Foctet-stream [following]
--2024-06-08 11:45:24-- https://objects.githubusercontent.com/github-production-release-asset-2e65be/46089560/d4d9a85e-64ab-4256-b18d-800f223e97d1?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=releaseassetproduction%2F20240608%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240608T114524Z&X-Amz-Expires=300&X-Amz-Signature=4fc5b909e2dfdec95c403a2e81a239decb94dbc8205511188b3f4008f890bc78&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=46089560&response-content-disposition=attachment%3B%20filename%3Dcontainerd-1.7.0-linux-amd64.tar.gz&response-content-type=application%2Foctet-stream
Resolving objects.githubusercontent.com (objects.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.110.133, ...
Connecting to objects.githubusercontent.com (objects.githubusercontent.com)|185.199.108.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 46625290 (44M) [application/octet-stream]
Saving to: ‘containerd-1.7.0-linux-amd64.tar.gz’
containerd-1.7.0 30%[===========> ] 13.48M 22.5MB/s l containerd-1.7.0- 40%[===============> ] 18.15M 22.7MB/s
containerd-1.7.0-linux-amd6 100%[========================================>] 44.46M 20.7MB/s in 2.1s
2024-06-08 11:45:27 (20.7 MB/s) - ‘containerd-1.7.0-linux-amd64.tar.gz’ saved [46625290/46625290]
# 다운받은 tar 파일 확인
$ ls
containerd-1.7.0-linux-amd64.tar.gz
# /usr/local 경로에 tar 파일 풀기
$ sudo tar Cxzvf /usr/local containerd-1.7.0-linux-amd64.tar.gz
bin/
bin/containerd-shim-runc-v1
bin/containerd
bin/containerd-shim-runc-v2
bin/containerd-shim
bin/ctr
bin/containerd-stress
# 설치된 containerd 버전 확인
$ containerd --version
containerd github.com/containerd/containerd v1.7.0 1fbd70374134b891f97ce19c70b6e50c7b9f4e0d
systemd로 설정하고 싶을 경우 아래와 같이 설정해준다. (바이너리 설치가 아닌 경우에는 자동으로 systemd로 설정됨)
https://github.com/containerd/containerd/blob/main/docs/getting-started.md#systemd
$ sudo mkdir -p /usr/local/lib/systemd/system
$ sudo vim /usr/local/lib/systemd/system/containerd.service
$ sudo cat /usr/local/lib/systemd/system/containerd.service
# Copyright The containerd Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
[Unit]
Description=containerd container runtime
Documentation=https://containerd.io
After=network.target local-fs.target
[Service]
ExecStartPre=-/sbin/modprobe overlay
ExecStart=/usr/local/bin/containerd
Type=notify
Delegate=yes
KillMode=process
Restart=always
RestartSec=5
# Having non-zero Limit*s causes performance problems due to accounting overhead
# in the kernel. We recommend using cgroups to do container-local accounting.
LimitNPROC=infinity
LimitCORE=infinity
# Comment TasksMax if your systemd version does not supports it.
# Only systemd 226 and above support this version.
TasksMax=infinity
OOMScoreAdjust=-999
[Install]
WantedBy=multi-user.target
# 데몬 리로드
$ sudo systemctl daemon-reload
# containerd 데몬 활성화
$ sudo systemctl enable --now containerd
Created symlink /etc/systemd/system/multi-user.target.wants/containerd.service → /usr/local/lib/systemd/system/containerd.service.
# containerd 데몬 상태 확인
$ sudo systemctl status containerd
● containerd.service - containerd container runtime
Loaded: loaded (/usr/local/lib/systemd/system/containerd.service; enabled; vendor preset: enabled)
Active: active (running) since Sat 2024-06-08 11:56:38 UTC; 4s ago
Docs: https://containerd.io
Process: 3097 ExecStartPre=/sbin/modprobe overlay (code=exited, status=0/SUCCESS)
Main PID: 3098 (containerd)
Tasks: 8
Memory: 13.9M
CPU: 97ms
CGroup: /system.slice/containerd.service
└─3098 /usr/local/bin/containerd
Jun 08 11:56:38 worker1 containerd[3098]: time="2024-06-08T11:56:38.315467147Z" level=info msg=serving... ad>
Jun 08 11:56:38 worker1 containerd[3098]: time="2024-06-08T11:56:38.315519281Z" level=info msg=serving... ad>
Jun 08 11:56:38 worker1 containerd[3098]: time="2024-06-08T11:56:38.317525419Z" level=info msg="Start subscr>
Jun 08 11:56:38 worker1 containerd[3098]: time="2024-06-08T11:56:38.317575729Z" level=info msg="Start recove>
Jun 08 11:56:38 worker1 containerd[3098]: time="2024-06-08T11:56:38.317639187Z" level=info msg="Start event >
Jun 08 11:56:38 worker1 containerd[3098]: time="2024-06-08T11:56:38.317659888Z" level=info msg="Start snapsh>
Jun 08 11:56:38 worker1 containerd[3098]: time="2024-06-08T11:56:38.317671613Z" level=info msg="Start cni ne>
Jun 08 11:56:38 worker1 containerd[3098]: time="2024-06-08T11:56:38.317681712Z" level=info msg="Start stream>
Jun 08 11:56:38 worker1 systemd[1]: Started containerd container runtime.
Jun 08 11:56:38 worker1 containerd[3098]: time="2024-06-08T11:56:38.319920221Z" level=info msg="containerd s>
2. runc 설치
(바이너리 설치가 아닌 경우에는 자동으로 runc 설치됨)
# runc 바이너리 다운로드
$ wget https://github.com/opencontainers/runc/releases/download/v1.1.12/runc.amd64
--2024-06-08 12:19:34-- https://github.com/opencontainers/runc/releases/download/v1.1.12/runc.amd64
Resolving github.com (github.com)... 20.248.137.48
Connecting to github.com (github.com)|20.248.137.48|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://objects.githubusercontent.com/github-production-release-asset-2e65be/36960321/d0ba447a-440a-43bd-a9eb-a8a2b071c200?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=releaseassetproduction%2F20240608%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240608T121917Z&X-Amz-Expires=300&X-Amz-Signature=e6f9e57e2e2f149b3e899d774dcb2c0f4df54188fa2079bae60d54c3d3b0904e&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=36960321&response-content-disposition=attachment%3B%20filename%3Drunc.amd64&response-content-type=application%2Foctet-stream [following]
--2024-06-08 12:19:34-- https://objects.githubusercontent.com/github-production-release-asset-2e65be/36960321/d0ba447a-440a-43bd-a9eb-a8a2b071c200?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=releaseassetproduction%2F20240608%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240608T121917Z&X-Amz-Expires=300&X-Amz-Signature=e6f9e57e2e2f149b3e899d774dcb2c0f4df54188fa2079bae60d54c3d3b0904e&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=36960321&response-content-disposition=attachment%3B%20filename%3Drunc.amd64&response-content-type=application%2Foctet-stream
Resolving objects.githubusercontent.com (objects.githubusercontent.com)... 185.199.111.133, 185.199.108.133, 185.199.109.133, ...
Connecting to objects.githubusercontent.com (objects.githubusercontent.com)|185.199.111.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 10709696 (10M) [application/octet-stream]
Saving to: ‘runc.amd64’
runc.amd64 100%[========================================>] 10.21M --.-KB/s in 0.08s
2024-06-08 12:19:35 (134 MB/s) - ‘runc.amd64’ saved [10709696/10709696]
# 다운된 파일 확인
$ ls
containerd-1.7.0-linux-amd64.tar.gz runc.amd64
# /usr/local/sbin/runc 경로에 설치
$ sudo install -m 755 runc.amd64 /usr/local/sbin/runc
3. CNI plugin 설치
(바이너리 설치가 아닌 경우에는 자동으로 runc 설치됨)
# cni-plugins 바이너리 다운로드
$ wget https://github.com/containernetworking/plugins/releases/download/v1.3.0/cni-plugins-linux-amd64-v1.3.0.tgz
--2024-06-08 12:22:15-- https://github.com/containernetworking/plugins/releases/download/v1.3.0/cni-plugins-linux-amd64-v1.3.0.tgz
Resolving github.com (github.com)... 20.248.137.48
Connecting to github.com (github.com)|20.248.137.48|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://objects.githubusercontent.com/github-production-release-asset-2e65be/84575398/d1ad8456-0aa1-4bb9-84e3-4e03286b4e9f?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=releaseassetproduction%2F20240608%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240608T122215Z&X-Amz-Expires=300&X-Amz-Signature=a887c07c860b0643071e91c2422b08e4f5a9601c8d667e63198f6861694f0e5d&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=84575398&response-content-disposition=attachment%3B%20filename%3Dcni-plugins-linux-amd64-v1.3.0.tgz&response-content-type=application%2Foctet-stream [following]
--2024-06-08 12:22:15-- https://objects.githubusercontent.com/github-production-release-asset-2e65be/84575398/d1ad8456-0aa1-4bb9-84e3-4e03286b4e9f?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=releaseassetproduction%2F20240608%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240608T122215Z&X-Amz-Expires=300&X-Amz-Signature=a887c07c860b0643071e91c2422b08e4f5a9601c8d667e63198f6861694f0e5d&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=84575398&response-content-disposition=attachment%3B%20filename%3Dcni-plugins-linux-amd64-v1.3.0.tgz&response-content-type=application%2Foctet-stream
Resolving objects.githubusercontent.com (objects.githubusercontent.com)... 185.199.110.133, 185.199.111.133, 185.199.108.133, ...
Connecting to objects.githubusercontent.com (objects.githubusercontent.com)|185.199.110.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 45338194 (43M) [application/octet-stream]
Saving to: ‘cni-plugins-linux-amd64-v1.3.0.tgz’
cni-plugins-linux-amd64-v1. 100%[========================================>] 43.24M 137MB/s in 0.3s
2024-06-08 12:22:16 (137 MB/s) - ‘cni-plugins-linux-amd64-v1.3.0.tgz’ saved [45338194/45338194]
# cni-plugins 파일 확인
$ ls
cni-plugins-linux-amd64-v1.3.0.tgz containerd-1.7.0-linux-amd64.tar.gz runc.amd64
# cni-plugins 파일을 위한 디렉토리 생성
$ sudo mkdir -p /opt/cni/bin
# cni-plugins 파일 /opt/cni/bin 경로에 풀기
$ sudo tar Cxzvf /opt/cni/bin cni-plugins-linux-amd64-v1.3.0.tgz
./
./loopback
./bandwidth
./ptp
./vlan
./host-device
./tuning
./vrf
./sbr
./tap
./dhcp
./static
./firewall
./macvlan
./dummy
./bridge
./ipvlan
./portmap
./host-local
4. config.toml 설정
# containerd 설정파일 생성
$ containerd config default | sudo tee /etc/containerd/config.toml
# containerd 설정파일인 contig.toml 기본 설정은 아래와 같다
$ cat /etc/containerd/config.toml
disabled_plugins = []
imports = []
oom_score = 0
plugin_dir = ""
required_plugins = []
root = "/var/lib/containerd"
state = "/run/containerd"
temp = ""
version = 2
[cgroup]
path = ""
[debug]
address = ""
format = ""
gid = 0
level = ""
uid = 0
[grpc]
address = "/run/containerd/containerd.sock"
gid = 0
max_recv_message_size = 16777216
max_send_message_size = 16777216
tcp_address = ""
tcp_tls_ca = ""
tcp_tls_cert = ""
tcp_tls_key = ""
uid = 0
[metrics]
address = ""
grpc_histogram = false
[plugins]
[plugins."io.containerd.gc.v1.scheduler"]
deletion_threshold = 0
mutation_threshold = 100
pause_threshold = 0.02
schedule_delay = "0s"
startup_delay = "100ms"
[plugins."io.containerd.grpc.v1.cri"]
cdi_spec_dirs = ["/etc/cdi", "/var/run/cdi"]
device_ownership_from_security_context = false
disable_apparmor = false
disable_cgroup = false
disable_hugetlb_controller = true
disable_proc_mount = false
disable_tcp_service = true
drain_exec_sync_io_timeout = "0s"
enable_cdi = false
enable_selinux = false
enable_tls_streaming = false
enable_unprivileged_icmp = false
enable_unprivileged_ports = false
ignore_image_defined_volumes = false
image_pull_progress_timeout = "1m0s"
max_concurrent_downloads = 3
max_container_log_line_size = 16384
netns_mounts_under_state_dir = false
restrict_oom_score_adj = false
sandbox_image = "registry.k8s.io/pause:3.8"
selinux_category_range = 1024
stats_collect_period = 10
stream_idle_timeout = "4h0m0s"
stream_server_address = "127.0.0.1"
stream_server_port = "0"
systemd_cgroup = false
tolerate_missing_hugetlb_controller = true
unset_seccomp_profile = ""
[plugins."io.containerd.grpc.v1.cri".cni]
bin_dir = "/opt/cni/bin"
conf_dir = "/etc/cni/net.d"
conf_template = ""
ip_pref = ""
max_conf_num = 1
setup_serially = false
[plugins."io.containerd.grpc.v1.cri".containerd]
default_runtime_name = "runc"
disable_snapshot_annotations = true
discard_unpacked_layers = false
ignore_blockio_not_enabled_errors = false
ignore_rdt_not_enabled_errors = false
no_pivot = false
snapshotter = "overlayfs"
[plugins."io.containerd.grpc.v1.cri".containerd.default_runtime]
base_runtime_spec = ""
cni_conf_dir = ""
cni_max_conf_num = 0
container_annotations = []
pod_annotations = []
privileged_without_host_devices = false
privileged_without_host_devices_all_devices_allowed = false
runtime_engine = ""
runtime_path = ""
runtime_root = ""
runtime_type = ""
sandbox_mode = ""
snapshotter = ""
[plugins."io.containerd.grpc.v1.cri".containerd.default_runtime.options]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
base_runtime_spec = ""
cni_conf_dir = ""
cni_max_conf_num = 0
container_annotations = []
pod_annotations = []
privileged_without_host_devices = false
privileged_without_host_devices_all_devices_allowed = false
runtime_engine = ""
runtime_path = ""
runtime_root = ""
runtime_type = "io.containerd.runc.v2"
sandbox_mode = "podsandbox"
snapshotter = ""
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
BinaryName = ""
CriuImagePath = ""
CriuPath = ""
CriuWorkPath = ""
IoGid = 0
IoUid = 0
NoNewKeyring = false
NoPivotRoot = false
Root = ""
ShimCgroup = ""
SystemdCgroup = false
[plugins."io.containerd.grpc.v1.cri".containerd.untrusted_workload_runtime]
base_runtime_spec = ""
cni_conf_dir = ""
cni_max_conf_num = 0
container_annotations = []
pod_annotations = []
privileged_without_host_devices = false
privileged_without_host_devices_all_devices_allowed = false
runtime_engine = ""
runtime_path = ""
runtime_root = ""
runtime_type = ""
sandbox_mode = ""
snapshotter = ""
[plugins."io.containerd.grpc.v1.cri".containerd.untrusted_workload_runtime.options]
[plugins."io.containerd.grpc.v1.cri".image_decryption]
key_model = "node"
[plugins."io.containerd.grpc.v1.cri".registry]
config_path = ""
[plugins."io.containerd.grpc.v1.cri".registry.auths]
[plugins."io.containerd.grpc.v1.cri".registry.configs]
[plugins."io.containerd.grpc.v1.cri".registry.headers]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors]
[plugins."io.containerd.grpc.v1.cri".x509_key_pair_streaming]
tls_cert_file = ""
tls_key_file = ""
[plugins."io.containerd.internal.v1.opt"]
path = "/opt/containerd"
[plugins."io.containerd.internal.v1.restart"]
interval = "10s"
[plugins."io.containerd.internal.v1.tracing"]
sampling_ratio = 1.0
service_name = "containerd"
[plugins."io.containerd.metadata.v1.bolt"]
content_sharing_policy = "shared"
[plugins."io.containerd.monitor.v1.cgroups"]
no_prometheus = false
[plugins."io.containerd.nri.v1.nri"]
disable = true
disable_connections = false
plugin_config_path = "/etc/nri/conf.d"
plugin_path = "/opt/nri/plugins"
plugin_registration_timeout = "5s"
plugin_request_timeout = "2s"
socket_path = "/var/run/nri/nri.sock"
[plugins."io.containerd.runtime.v1.linux"]
no_shim = false
runtime = "runc"
runtime_root = ""
shim = "containerd-shim"
shim_debug = false
[plugins."io.containerd.runtime.v2.task"]
platforms = ["linux/amd64"]
sched_core = false
[plugins."io.containerd.service.v1.diff-service"]
default = ["walking"]
[plugins."io.containerd.service.v1.tasks-service"]
blockio_config_file = ""
rdt_config_file = ""
[plugins."io.containerd.snapshotter.v1.aufs"]
root_path = ""
[plugins."io.containerd.snapshotter.v1.btrfs"]
root_path = ""
[plugins."io.containerd.snapshotter.v1.devmapper"]
async_remove = false
base_image_size = ""
discard_blocks = false
fs_options = ""
fs_type = ""
pool_name = ""
root_path = ""
[plugins."io.containerd.snapshotter.v1.native"]
root_path = ""
[plugins."io.containerd.snapshotter.v1.overlayfs"]
root_path = ""
upperdir_label = false
[plugins."io.containerd.snapshotter.v1.zfs"]
root_path = ""
[plugins."io.containerd.tracing.processor.v1.otlp"]
endpoint = ""
insecure = false
protocol = ""
[plugins."io.containerd.transfer.v1.local"]
[proxy_plugins]
[stream_processors]
[stream_processors."io.containerd.ocicrypt.decoder.v1.tar"]
accepts = ["application/vnd.oci.image.layer.v1.tar+encrypted"]
args = ["--decryption-keys-path", "/etc/containerd/ocicrypt/keys"]
env = ["OCICRYPT_KEYPROVIDER_CONFIG=/etc/containerd/ocicrypt/ocicrypt_keyprovider.conf"]
path = "ctd-decoder"
returns = "application/vnd.oci.image.layer.v1.tar"
[stream_processors."io.containerd.ocicrypt.decoder.v1.tar.gzip"]
accepts = ["application/vnd.oci.image.layer.v1.tar+gzip+encrypted"]
args = ["--decryption-keys-path", "/etc/containerd/ocicrypt/keys"]
env = ["OCICRYPT_KEYPROVIDER_CONFIG=/etc/containerd/ocicrypt/ocicrypt_keyprovider.conf"]
path = "ctd-decoder"
returns = "application/vnd.oci.image.layer.v1.tar+gzip"
[timeouts]
"io.containerd.timeout.bolt.open" = "0s"
"io.containerd.timeout.metrics.shimstats" = "2s"
"io.containerd.timeout.shim.cleanup" = "5s"
"io.containerd.timeout.shim.load" = "5s"
"io.containerd.timeout.shim.shutdown" = "3s"
"io.containerd.timeout.task.state" = "2s"
[ttrpc]
address = ""
gid = 0
uid = 0
5. cgroup 설정
https://v1-27.docs.kubernetes.io/docs/tasks/administer-cluster/kubeadm/configure-cgroup-driver/
containerd 설정파일인 config.toml 파일에서 SystemCgroup 값을 false 에서 true로 변경한다.
$ sudo vim /etc/containerd/config.toml
# 다음과 같이 변경
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
...
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true # false
Linux에서는 제어 그룹(cgroup)을 사용하여 프로세스에 할당되는 리소스를 제한한다.
kubelet과 container runtime은 모두 cgroup을 인터페이스하여 파드 및 컨테이너에 대한 리소스를 관리하기 위해 cgroup을 사용한다. 그러기 위해서는 kubelet과 container runtime이 동일한 cgroup 드라이버를 사용하고 동일하게 구성되는 것이 중요하다.
cgroup 드라이버의 종류는 2가지가 있다.
cgroupfs | - kubelet의 기본 cgroup 드라이버이다 - systemd가 init 시스템인 경우 cgroupfs 드라이버는 권장되지 않는다 (systemd는 시스템에 단일 cgroup 관리자가 있을 것으로 예상하기 때문에) - cgroup v2를 사용하는 경우 cgroupfs 대신 systemd cgroup 드라이버를 사용해야한다 |
systemd | - systemd가 Linux의 init 시스템으로 선택되면 init 프로세스는 cgroup을 생성 및 사용하고 cgroup 관리자 역할을 한다 - systemd를 cgroupfs 드라이버와 함께 init 시스템으로 사용하면 시스템은 두 개의 서로 다른 cgroup 관리자를 갖게 된다 - cgroup 관리자가 여러 개일 경우 문제가 생길 수 있다 |
6. IPv4를 포워딩하여 iptables가 브리지된 트래픽을 보게 하기
overlay와 br_netfilter 커널 모듈을 활성화하는 이유는 쿠버네티스가 컨테이너 관리 및 네트워킹을 효과적으로 수행할 수 있도록 해주기 위해서이다.
overlay(overlayfs) | - overlayfs는 여러 개의 파일 시스템을 겹쳐서 하나처럼 보이게 하는 유닉스 파일 시스템 유형 중 하나이다 - 쿠버네티스에서는 다음과 같은 목적으로 overlayfs를 사용한다 - 효율적인 이미지 저장 및 관리 - 빠른 컨테이너 시작 |
br_netfilter | - br_netfilter 모듈은 리눅스 브리지와 네트워크 필터링 기능을 연결한다 - 이 모듈이 쿠버네티스에서 중요한 이유는 다음과 같다 - 네트워크 트래픽 관리 - 네트워크 보안 강화 - CNI 플로그인과의 호환성 |
$ lsmod | grep br_netfilter
$ sudo modprobe br_netfilter
$ lsmod | grep br_netfilter
br_netfilter 32768 0
bridge 409600 1 br_netfilter
$ cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
overlay
br_netfilter
$ cat /etc/modules-load.d/k8s.conf
overlay
br_netfilter
$ sudo modprobe overlay
$ sudo modprobe br_netfilter
$ cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
$ sudo sysctl --system
Kubeadm, Kubelet, Kubectl 설치
https://v1-27.docs.kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/
kubeadm | 클러스터를 부트스트랩하는 명령 |
kubelet | 클러스터의 모든 머신에서 실행되는 파드와 컨테이너 시작과 같은 작업을 수행하는 컴포넌트 |
kubectl | 클러스터와 통신하기 위한 커맨드라인 유틸리티 |
## kubernetes apt 저장소를 사용하는 데 필요한 패키지 설치
$ sudo apt-get update
# apt-transport-https may be a dummy package; if so, you can skip that package
$ sudo apt-get install -y apt-transport-https ca-certificates curl
## kubernetes 패키지 저장소의 공개 서명키 다운로드
$ curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.27/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
## kubernetes apt 저장소 추가 (아래 저장소에는 kubernetes 1.27 버전의 패키지만 존재)
# This overwrites any existing configuration in /etc/apt/sources.list.d/kubernetes.list
$ echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.27/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
## kubelet, kubeadm, kubectl 패키지 설치
$ sudo apt-get update
$ sudo apt-get install -y kubelet kubeadm kubectl
$ sudo apt-mark hold kubelet kubeadm kubectl
HAProxy 설정
master가 1개일 경우 HAProxy를 사용하지 않아도 된다.
master를 여러 개(multi master)로 설정할 경우 LB역할이 필요하기 때문에 LB역할을 위해 HAProxy를 구축하여 사용하였다.
해당 예시에서는 master를 1개로 구성하였으나, multi master로 구축한다는 가정하에 HAProxy를 설정하였다.
ubuntu에서 haproxy를 설치하면 /etc/haproxy/haproxy.cfg 경로에 기본 설정 파일이 생성된다.
$ sudo apt update
$ sudo apt install haproxy -y
$ cat /etc/haproxy/haproxy.cfg
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
stats timeout 30s
user haproxy
group haproxy
daemon
# Default SSL material locations
ca-base /etc/ssl/certs
crt-base /etc/ssl/private
# See: https://ssl-config.mozilla.org/#server=haproxy&server-version=2.0.3&config=intermediate
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000
timeout client 50000
timeout server 50000
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http
기본 설정 하위에 master proxy를 위한 설정을 추가해준다. 원래 여러 개의 master에 대해 설정을 해줘야 의미있지만 현재 환경에서 master를 1개로 구성하였으므로 1개의 master에 대한 설정만 해준다.
$ cat /etc/haproxy/haproxy.cfg
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
stats timeout 30s
user haproxy
group haproxy
daemon
# Default SSL material locations
ca-base /etc/ssl/certs
crt-base /etc/ssl/private
# See: https://ssl-config.mozilla.org/#server=haproxy&server-version=2.0.3&config=intermediate
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000
timeout client 50000
timeout server 50000
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http
frontend master
bind *:6443
mode http
default_backend master
backend master
balance roundrobin
server master1 172.31.10.203:6443 check
Kubeadm Init
kubeadm init은 kubeadm을 통해 쿠버네티스 클러스터를 구축해주는 명령이다.
kubeadm init --control-plane-endpoint=172.31.6.142:6443 이런 식으로 옵션을 통해 init도 가능하지만, config 파일을 정의해두고 init을 하도록 아래와 같이 설정하였다.
# kubeadm init을 위한 기본 config 파일 생성 명령
$ kubeadm config print init-defaults
apiVersion: kubeadm.k8s.io/v1beta3
bootstrapTokens:
- groups:
- system:bootstrappers:kubeadm:default-node-token
token: abcdef.0123456789abcdef
ttl: 24h0m0s
usages:
- signing
- authentication
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: 1.2.3.4
bindPort: 6443
nodeRegistration:
criSocket: unix:///var/run/containerd/containerd.sock
imagePullPolicy: IfNotPresent
name: node
taints: null
---
apiServer:
timeoutForControlPlane: 4m0s
apiVersion: kubeadm.k8s.io/v1beta3
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
controllerManager: {}
dns: {}
etcd:
local:
dataDir: /var/lib/etcd
imageRepository: registry.k8s.io
kind: ClusterConfiguration
kubernetesVersion: 1.27.0
networking:
dnsDomain: cluster.local
serviceSubnet: 10.96.0.0/12
scheduler: {}
나의 설정에 맞게 아래와 같이 수정한다.
$ cat kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: 172.31.10.203 # 최초 init node IP, 마스터 여러 개일 경우 아무거나 해도 가능
bindPort: 6443
nodeRegistration:
criSocket: /run/containerd/containerd.sock #unix:///var/run/containerd/containerd.sock
imagePullPolicy: IfNotPresent
name: master1
---
apiServer:
timeoutForControlPlane: 4m0s
apiVersion: kubeadm.k8s.io/v1beta3
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
controllerManager: {}
dns: {}
etcd:
local:
dataDir: /var/lib/etcd
imageRepository: registry.k8s.io
kind: ClusterConfiguration
kubernetesVersion: 1.27.0
controlPlaneEndpoint: "172.31.6.142:6443" # apiserver의 endpoint로 작성 (master 1대로 구성 시 해당 노드IP:6443포트로 지정 / haproxy등 LB 구성하여 master와 통신 시 해당 LB IP와 LB에 매핑한 포트로 지정)
networking:
dnsDomain: cluster.local
serviceSubnet: 10.96.0.0/16
podSubnet: 10.244.0.0/24
scheduler: {}
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd # kubelet의 cgroup driver를 systemd로 설정
kubeadm init config 작성 완료 후 아래와 같이 init 명령 입력하면 성공
$ kubeadm init --config=kubeadm-config.yaml
Your Kubernetes control-plane has initialized successfully!
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Alternatively, if you are the root user, you can run:
export KUBECONFIG=/etc/kubernetes/admin.conf
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
https://kubernetes.io/docs/concepts/cluster-administration/addons/
You can now join any number of control-plane nodes by copying certificate authorities
and service account keys on each node and then running the following as root:
kubeadm join 172.31.6.142:6443 --token e92a41.b5iey87t0664yfv9 \
--discovery-token-ca-cert-hash sha256:ec395e10edda549081fd13242778aaec0f60802477d94791e9c38f60ca370a55 \
--control-plane
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join 172.31.6.142:6443 --token e92a41.b5iey87t0664yfv9 \
--discovery-token-ca-cert-hash sha256:ec395e10edda549081fd13242778aaec0f60802477d94791e9c38f60ca370a55
kubeadm init이 성공하면 하위 메시지가 나오는데, 잘 읽어보고 따라하면 된다.
kubeadm init이 성공하면 kubeconfig 파일이 생성되는데, 클러스터에 대한 정보가 들어있어 쿠버네티스 통신을 위해 필요하다.
보통 kubeconfig 파일은 ~/.kube/config 경로에 존재하는 게 국룰이므로 아래와 같이 /etc/kubernetes/admin.conf에 저장된 kubeconfig 파일을 ~/.kube/config로 옮겨준다.
$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config
$ sudo vim ~/.bashrc
# 맨 아래 아래 줄 삽입
export KUBECONFIG=~/.kube/config
$ source ~/.bashrc
Kubeadm Join
kubeadm init은 master 노드에서 진행하는 것이므로 아직 worker 노드는 join되지 못했다.
master 노드에서 init이 성공하면 kubeadm join과 관련된 명령이 나오게 된다.
kubeadm join도 init과 같이 config 파일을 통해 가능하다.
$ kubeadm config print join-defaults
apiVersion: kubeadm.k8s.io/v1beta3
caCertPath: /etc/kubernetes/pki/ca.crt
discovery:
bootstrapToken:
apiServerEndpoint: kube-apiserver:6443
token: abcdef.0123456789abcdef
unsafeSkipCAVerification: true
timeout: 5m0s
tlsBootstrapToken: abcdef.0123456789abcdef
kind: JoinConfiguration
nodeRegistration:
criSocket: unix:///var/run/containerd/containerd.sock
imagePullPolicy: IfNotPresent
name: worker1
taints: null
하지만 이번 테스트에서는 join config 파일을 통해 진행하지 않고 master init시 나온 join 명령어를 그대로 복붙하여 진행하였다.
$ sudo kubeadm join 172.31.6.142:6443 --token <TOKEN> \
--discovery-token-ca-cert-hash sha256:<HASH>
[preflight] Running pre-flight checks
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...
This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.
Run 'kubectl get nodes' on the control-plane to see this node join the cluster.
CNI install
master의 init과 worker의 join이 완료된 후에 master 노드에서 kubectl get nodes로 노드 상태를 조회해보면 다음과 같다.
노드가 아직 NotReady 상태이다. 이는 아직 CNI를 설치하지 않아서 그렇다.
ubuntu@master1:~$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
node NotReady control-plane 22m v1.27.14
worker1 NotReady <none> 9m9s v1.27.14
kubectl get pods 명령으로 pod 상태를 확인해보면 coredns만 Pending 상태인 것을 볼 수 있다.
이 또한 CNI가 설치되지 않아서다.
ubuntu@master1:~$ kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-5d78c9869d-mxjnk 0/1 Pending 0 8m26s
kube-system coredns-5d78c9869d-v9v5b 0/1 Pending 0 8m26s
kube-system etcd-master 1/1 Running 0 8m39s
kube-system kube-apiserver-master 1/1 Running 0 8m40s
kube-system kube-controller-manager-master 1/1 Running 0 8m39s
kube-system kube-proxy-d7rnn 1/1 Running 0 8m26s
kube-system kube-proxy-qr5z2 1/1 Running 0 6m53s
kube-system kube-scheduler-master 1/1 Running 0 8m39s
tigera-operator tigera-operator-94665859f-xkkwd 1/1 Running 0 35s
calico 공식 문서에서 설치 가이드를 따라 설치를 하면 되는데,
calico 설치를 위해 사용할 리소스 메니페스트에서 pod CIDR과 calico CIDR을 일치시켜줘야 하는 부분이 있기 때문에 wget을 통해 파일을 다운 받는다.
ubuntu@master1:~$ wget https://raw.githubusercontent.com/projectcalico/calico/v3.28.0/manifests/custom-resources.yaml
--2024-06-14 13:47:40-- https://raw.githubusercontent.com/projectcalico/calico/v3.28.0/manifests/custom-resources.yaml
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.110.133, 185.199.111.133, 185.199.109.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.110.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 777 [text/plain]
Saving to: ‘custom-resources.yaml’
custom-resources.yaml 100%[================================================================================================================>] 777 --.-KB/s in 0s
2024-06-14 13:47:40 (44.5 MB/s) - ‘custom-resources.yaml’ saved [777/777]
다운받은 파일은 아래와 같다.
cidr: 192.168.0.0/16 라고 기본적으로 돼있는데
ubuntu@master1:~$ cat custom-resources.yaml
# This section includes base Calico installation configuration.
# For more information, see: https://docs.tigera.io/calico/latest/reference/installation/api#operator.tigera.io/v1.Installation
apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
name: default
spec:
# Configures Calico networking.
calicoNetwork:
ipPools:
- name: default-ipv4-ippool
blockSize: 26
cidr: 10.244.0.0/24 #192.168.0.0/16
encapsulation: VXLANCrossSubnet
natOutgoing: Enabled
nodeSelector: all()
---
# This section configures the Calico API server.
# For more information, see: https://docs.tigera.io/calico/latest/reference/installation/api#operator.tigera.io/v1.APIServer
apiVersion: operator.tigera.io/v1
kind: APIServer
metadata:
name: default
spec: {}
위에서 쿠버네티스 설치를 위해 사용한 config 파일을 보면 아래와 같이 10.244.0.0/24 로 돼있기 때문에 calico CIDR을 pod CIDR과 똑같이 바꿔준다.
ubuntu@master1:~$ cat kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: 172.31.10.203
bindPort: 6443
nodeRegistration:
criSocket: /run/containerd/containerd.sock #unix:///var/run/containerd/containerd.sock
imagePullPolicy: IfNotPresent
name: master
---
apiServer:
timeoutForControlPlane: 4m0s
apiVersion: kubeadm.k8s.io/v1beta3
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
controllerManager: {}
dns: {}
etcd:
local:
dataDir: /var/lib/etcd
imageRepository: registry.k8s.io
kind: ClusterConfiguration
kubernetesVersion: 1.27.0
controlPlaneEndpoint: "172.31.6.142:6443"
networking:
dnsDomain: cluster.local
serviceSubnet: 10.96.0.0/16
podSubnet: 10.244.0.0/24
scheduler: {}
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
ubuntu@master1:~$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
master Ready control-plane 13m v1.27.14
worker1 Ready <none> 12m v1.27.14
ubuntu@master1:~$
참고 링크
https://ykarma1996.tistory.com/192
'클라우드 > Kubernetes(쿠버네티스)' 카테고리의 다른 글
[kubernetes] Service (Headless Service, ExternalName), Endpoint (0) | 2024.08.13 |
---|---|
[Kubernetes] ArgoCD (0) | 2024.07.30 |
[kubernetes] kubernetes의 update 종류와 동작 방법 (apply, edit, patch, replace) (0) | 2023.08.18 |
[kubernetes] pod lifecycle (0) | 2023.08.10 |
[kubernetes] kube-proxy (0) | 2023.08.03 |