# 작업 디렉토리 생성
mkdir -p ~/k8s-packages
cd ~/k8s-packages
# 기본 의존성 패키지들 (Rocky Linux 9 기준)
dnf download --downloadonly --downloaddir=. \
curl wget socat conntrack ebtables \
bash-completion tree net-tools \
ca-certificates gnupg2
# Docker/Containerd 관련
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
dnf download --downloadonly --downloaddir=. \
docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Kubernetes 관련 (Rocky Linux 9 용)
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.32/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.32/rpm/repodata/repomd.xml.key
EOF
dnf download --downloadonly --downloaddir=. \
kubelet-1.32.0 kubeadm-1.32.0 kubectl-1.32.0 cri-tools kubernetes-cni
# 패키지 압축
tar -czf k8s-packages.tar.gz *.rpm
echo "📦 다운로드된 RPM 패키지들:"
ls -la *.rpm
echo ""
echo "패키지 총 크기:"
du -h k8s-packages.tar.gz
# Docker 설치 (Bastion Host에서만)
sudo dnf install -y docker-ce docker-ce-cli containerd.io
sudo systemctl start docker
sudo systemctl enable docker
sudo usermod -aG docker $USER
# 로그아웃 후 재로그인 필요
# 이미지 저장 디렉토리 생성
mkdir -p ~/k8s-images
cd ~/k8s-images
# Kubernetes 핵심 이미지들
KUBERNETES_VERSION="v1.32.0"
IMAGES=(
"registry.k8s.io/kube-apiserver:${KUBERNETES_VERSION}"
"registry.k8s.io/kube-controller-manager:${KUBERNETES_VERSION}"
"registry.k8s.io/kube-scheduler:${KUBERNETES_VERSION}"
"registry.k8s.io/kube-proxy:${KUBERNETES_VERSION}"
"registry.k8s.io/pause:3.10"
"registry.k8s.io/pause:3.8"
"registry.k8s.io/coredns/coredns:v1.11.3"
"registry.k8s.io/etcd:3.5.16-0"
)
# Cilium 이미지들 (Calico 대신)
CILIUM_IMAGES=(
"quay.io/cilium/cilium:v1.16.5"
"quay.io/cilium/operator-generic:v1.16.5"
"quay.io/cilium/hubble-relay:v1.16.5"
"quay.io/cilium/hubble-ui:v0.13.1"
"quay.io/cilium/hubble-ui-backend:v0.13.1"
"quay.io/cilium/certgen:v0.2.0"
)
# 기타 유용한 이미지들
ADDITIONAL_IMAGES=(
"quay.io/metallb/controller:v0.13.12"
"quay.io/metallb/speaker:v0.13.12"
"registry.k8s.io/ingress-nginx/controller:v1.12.0-beta.0"
"registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.4.4"
"registry.k8s.io/metrics-server/metrics-server:v0.7.2"
"kubernetesui/dashboard:v2.7.0"
"kubernetesui/metrics-scraper:v1.0.8"
"nginx:1.27.2-alpine"
"redis:latest"
"registry:2"
)
# 모든 이미지 다운로드 및 저장
ALL_IMAGES=("${IMAGES[@]}" "${CILIUM_IMAGES[@]}" "${ADDITIONAL_IMAGES[@]}")
for image in "${ALL_IMAGES[@]}"; do
echo "🔽 Pulling $image..."
docker pull $image
# 파일명 생성 (특수문자 제거)
filename=$(echo $image | sed 's/[\/:]/-/g').tar
docker save $image -o $filename
echo "✅ Saved as $filename"
done
# 모든 이미지를 하나의 tar 파일로 압축
tar -czf k8s-images.tar.gz *.tar
echo "✅ All images compressed to k8s-images.tar.gz"
# Cilium CLI 다운로드
CILIUM_CLI_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/cilium-cli/main/stable.txt)
CLI_ARCH=amd64
curl -L --fail --remote-name-all https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}
sha256sum --check cilium-linux-${CLI_ARCH}.tar.gz.sha256sum
sudo tar xzvfC cilium-linux-${CLI_ARCH}.tar.gz /usr/local/bin
rm cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}
# Cilium manifest 생성
curl -L https://raw.githubusercontent.com/cilium/cilium/v1.16.5/install/kubernetes/quick-install.yaml -o cilium-install.yaml
# 전송할 파일들 확인
ls -la ~/k8s-packages/k8s-packages.tar.gz
ls -la ~/k8s-images/k8s-images.tar.gz
ls -la cilium-install.yaml
# 파일 크기 확인
du -h ~/k8s-packages/k8s-packages.tar.gz
du -h ~/k8s-images/k8s-images.tar.gz
du -h cilium-install.yaml
# Bastion Host에서 Registry Node로 직접 전송
scp ~/k8s-packages/k8s-packages.tar.gz user@192.168.56.159:~/
scp ~/k8s-images/k8s-images.tar.gz user@192.168.56.159:~/
scp cilium-install.yaml user@192.168.56.159:~/
# Registry Node에서 다른 노드들로 분배
scp k8s-packages.tar.gz user@192.168.56.160:~/ # Master Node
scp k8s-packages.tar.gz user@192.168.56.161:~/ # Worker Node
# /etc/hosts 파일 설정
sudo tee -a /etc/hosts <<EOF
192.168.56.159 k8s-registry
192.168.56.160 k8s-master
192.168.56.161 k8s-worker1
EOF
# 호스트명 설정 (각 노드에서 각각 실행)
# Master Node에서:
# sudo hostnamectl set-hostname k8s-master
# Worker Node에서:
# sudo hostnamectl set-hostname k8s-worker1
# 전송받은 패키지 압축 해제
tar -xzf k8s-packages.tar.gz
# RPM 패키지 설치 (Rocky Linux 방식)
sudo rpm -ivh *x86_64.rpm *noarch.rpm --force --nodeps
# 의존성 문제 발생 시 강제 설치
# sudo rpm -ivh *.rpm --force --nodeps
# sudo dnf install -y ./*.rpm --skip-broken
# 스왑 비활성화
sudo swapoff -a
sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
# 스왑 비활성화 서비스 생성
sudo tee /etc/systemd/system/swapoff.service > /dev/null <<EOF
[Unit]
Description=Turn off all swap
DefaultDependencies=no
After=local-fs.target
[Service]
Type=oneshot
ExecStart=/sbin/swapoff -a
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
WantedBy=graphical.target
EOF
sudo systemctl enable swapoff
# 방화벽 비활성화
sudo systemctl disable firewalld
sudo systemctl stop firewalld
# SELinux 비활성화
sudo setenforce 0
sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config
# IP 포워딩 활성화
echo '1' | sudo tee /proc/sys/net/ipv4/ip_forward
# 커널 모듈 로드
sudo tee /etc/modules-load.d/containerd.conf > /dev/null <<EOF
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
# sysctl 설정
sudo tee /etc/sysctl.d/99-kubernetes-cri.conf > /dev/null <<EOF
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
sudo sysctl --system
# Docker 설정
sudo mkdir -p /etc/systemd/system/docker.service.d
sudo usermod -aG docker $USER
# Docker daemon.json 설정 (Private Registry 사용)
sudo tee /etc/docker/daemon.json > /dev/null <<EOF
{
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"storage-driver": "overlay2",
"insecure-registries": ["192.168.56.159:5000"]
}
EOF
# Docker 서비스 시작
sudo systemctl daemon-reload
sudo systemctl enable docker
sudo systemctl start docker
# Containerd 설정
sudo mkdir -p /etc/containerd
sudo containerd config default | sudo tee /etc/containerd/config.toml
# SystemdCgroup 활성화
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
# Private Registry 설정을 위해 config.toml 수정
sudo sed -i 's|sandbox_image = "registry.k8s.io/pause:3.8"|sandbox_image = "192.168.56.159:5000/pause:3.10"|' /etc/containerd/config.toml
# Private Registry mirror 설정 추가
sudo tee -a /etc/containerd/config.toml <<EOF
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."192.168.56.159:5000"]
endpoint = ["http://192.168.56.159:5000"]
[plugins."io.containerd.grpc.v1.cri".registry.configs."192.168.56.159:5000".tls]
insecure_skip_verify = true
EOF
sudo systemctl enable containerd
sudo systemctl restart containerd
# Kubelet 활성화
sudo systemctl enable kubelet
# Docker 설치 (Registry Node에서만)
tar -xzf k8s-packages.tar.gz
sudo rpm -ivh docker-ce*.rpm containerd.io*.rpm --force --nodeps
sudo systemctl start docker
sudo systemctl enable docker
sudo usermod -aG docker $USER
# Docker daemon.json 설정
sudo tee /etc/docker/daemon.json > /dev/null <<EOF
{
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"storage-driver": "overlay2",
"insecure-registries": ["192.168.56.159:5000"]
}
EOF
sudo systemctl restart docker
# Registry 데이터 디렉토리 생성
sudo mkdir -p /opt/registry/data
sudo chown -R $USER:$USER /opt/registry
# Registry 컨테이너 실행
docker run -d \
--name registry \
--restart=always \
-p 5000:5000 \
-v /opt/registry/data:/var/lib/registry \
-e REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/var/lib/registry \
registry:2
# Registry 상태 확인
docker ps | grep registry
curl http://localhost:5000/v2/_catalog
# 이미지 압축 해제
tar -xzf k8s-images.tar.gz
# 모든 이미지 로드
for tar_file in *.tar; do
echo "🔽 Loading $tar_file..."
docker load -i $tar_file
done
REGISTRY_IP="192.168.56.159"
# Kubernetes 이미지들 태그 변경 및 Push
echo "📤 Pushing Kubernetes images..."
docker tag registry.k8s.io/kube-apiserver:v1.32.0 ${REGISTRY_IP}:5000/kube-apiserver:v1.32.0
docker tag registry.k8s.io/kube-controller-manager:v1.32.0 ${REGISTRY_IP}:5000/kube-controller-manager:v1.32.0
docker tag registry.k8s.io/kube-scheduler:v1.32.0 ${REGISTRY_IP}:5000/kube-scheduler:v1.32.0
docker tag registry.k8s.io/kube-proxy:v1.32.0 ${REGISTRY_IP}:5000/kube-proxy:v1.32.0
docker tag registry.k8s.io/pause:3.10 ${REGISTRY_IP}:5000/pause:3.10
docker tag registry.k8s.io/pause:3.8 ${REGISTRY_IP}:5000/pause:3.8
docker tag registry.k8s.io/coredns/coredns:v1.11.3 ${REGISTRY_IP}:5000/coredns:v1.11.3
docker tag registry.k8s.io/etcd:3.5.15-0 ${REGISTRY_IP}:5000/etcd:3.5.15-0
# Cilium 이미지들
echo "📤 Pushing Cilium images..."
docker tag quay.io/cilium/cilium:v1.16.5 ${REGISTRY_IP}:5000/cilium:v1.16.5
docker tag quay.io/cilium/operator-generic:v1.16.5 ${REGISTRY_IP}:5000/cilium-operator:v1.16.5
docker tag quay.io/cilium/hubble-relay:v1.16.5 ${REGISTRY_IP}:5000/hubble-relay:v1.16.5
docker tag quay.io/cilium/hubble-ui:v0.13.1 ${REGISTRY_IP}:5000/hubble-ui:v0.13.1
docker tag quay.io/cilium/hubble-ui-backend:v0.13.1 ${REGISTRY_IP}:5000/hubble-ui-backend:v0.13.1
docker tag quay.io/cilium/certgen:v0.2.0 ${REGISTRY_IP}:5000/cilium-certgen:v0.2.0
# 모든 이미지 Push
echo "🚀 Starting image push to private registry..."
docker push ${REGISTRY_IP}:5000/kube-apiserver:v1.32.0
docker push ${REGISTRY_IP}:5000/kube-controller-manager:v1.32.0
docker push ${REGISTRY_IP}:5000/kube-scheduler:v1.32.0
docker push ${REGISTRY_IP}:5000/kube-proxy:v1.32.0
docker push ${REGISTRY_IP}:5000/pause:3.10
docker push ${REGISTRY_IP}:5000/pause:3.8
docker push ${REGISTRY_IP}:5000/coredns:v1.11.3
docker push ${REGISTRY_IP}:5000/etcd:3.5.15-0
docker push ${REGISTRY_IP}:5000/cilium:v1.16.5
docker push ${REGISTRY_IP}:5000/cilium-operator:v1.16.5
docker push ${REGISTRY_IP}:5000/hubble-relay:v1.16.5
docker push ${REGISTRY_IP}:5000/hubble-ui:v0.13.1
docker push ${REGISTRY_IP}:5000/hubble-ui-backend:v0.13.1
docker push ${REGISTRY_IP}:5000/cilium-certgen:v0.2.0
echo "✅ All images pushed to registry successfully!"
# Registry 내용 확인
curl http://localhost:5000/v2/_catalog | jq .
MASTER_IP="192.168.56.160"
REGISTRY_IP="192.168.56.159"
cat > kubeadm-config.yaml <<EOF
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: v1.32.0
imageRepository: ${REGISTRY_IP}:5000
networking:
podSubnet: 10.244.0.0/16
controlPlaneEndpoint: "${MASTER_IP}:6443"
apiServer:
certSANs:
- "${MASTER_IP}"
extraArgs:
advertise-address: "${MASTER_IP}"
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: ipvs
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
nodeRegistration:
criSocket: unix:///run/containerd/containerd.sock
kubeletExtraArgs:
pod-infra-container-image: ${REGISTRY_IP}:5000/pause:3.10
EOF
# Containerd 재시작
sudo systemctl restart containerd
# Kubeadm으로 클러스터 초기화
sudo kubeadm init --config=kubeadm-config.yaml
# 성공 시 kubectl 설정
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
# 조인 토큰 저장 (워커 노드에서 사용)
kubeadm token create --print-join-command > worker-join-command.txt
echo "📝 Worker join command saved to worker-join-command.txt"
# Master Node의 worker-join-command.txt 내용을 복사하여 실행
# 예시:
sudo kubeadm join 192.168.56.160:6443 --token <token> \
--discovery-token-ca-cert-hash sha256:<hash>
Cilium 에어갭 배포 완전 정리
# === 환경 구성 ===
- Bastion Host: 192.168.56.159 (Private Registry 포함)
- Master Node: 192.168.56.160
- Worker Node: 192.168.56.161
- Registry: http://192.168.56.159:5000
# === 외부 인터넷 환경에서 ===
# 필요한 이미지들 다운로드
docker pull quay.io/cilium/cilium:v1.16.5
docker pull quay.io/cilium/operator-generic:v1.16.5
docker pull quay.io/cilium/cilium-envoy:v1.30.8-xxxx
# tar 파일로 저장
docker save quay.io/cilium/cilium:v1.16.5 > cilium.tar
docker save quay.io/cilium/operator-generic:v1.16.5 > cilium-operator.tar
docker save quay.io/cilium/cilium-envoy:v1.30.8-xxxx > cilium-envoy.tar
# Bastion Host로 전송
scp *.tar root@192.168.56.159:/root/
# === Bastion Host에서 ===
# 이미지 로드
docker load < cilium.tar
docker load < cilium-operator.tar
docker load < cilium-envoy.tar
# Private Registry용 태그 설정
docker tag quay.io/cilium/cilium:v1.16.5 192.168.56.159:5000/cilium:v1.16.5
docker tag quay.io/cilium/operator-generic:v1.16.5 192.168.56.159:5000/cilium-operator:v1.16.5
docker tag quay.io/cilium/cilium-envoy:v1.30.8-xxxx 192.168.56.159:5000/cilium-envoy:v1.30.8-xxxx
# Registry에 푸시
docker push 192.168.56.159:5000/cilium:v1.16.5
docker push 192.168.56.159:5000/cilium-operator:v1.16.5
docker push 192.168.56.159:5000/cilium-envoy:v1.30.8-xxxx
# === Bastion Host에서 ===
# Cilium Helm 차트 다운로드 (외부에서)
helm repo add cilium https://helm.cilium.io/
helm pull cilium/cilium --version 1.16.5
tar -xzf cilium-1.16.5.tgz
# 매니페스트 생성
cd cilium/
helm template cilium . \
--namespace kube-system \
--set image.repository=192.168.56.159:5000/cilium \
--set image.tag=v1.16.5 \
--set operator.image.repository=192.168.56.159:5000/cilium-operator \
--set operator.image.tag=v1.16.5 \
--set envoy.image.repository=192.168.56.159:5000/cilium-envoy \
--set envoy.image.tag=v1.30.8-xxxx \
> ../cilium-install.yaml
# === Bastion Host에서 ===
# SHA256 해시 제거 (호환성 문제 해결)
sed -i 's/@sha256:[a-f0-9]\{64\}//g' cilium-install.yaml
# 이미지 이름 통일 (generic → 실제 태그명)
sed -i 's/cilium-operator-generic:v1.16.5/cilium-operator:v1.16.5/g' cilium-install.yaml
# Public Registry 참조 제거
sed -i 's|quay.io/cilium/cilium-envoy|192.168.56.159:5000/cilium-envoy|g' cilium-install.yaml
# === Bastion Host → Master Node ===
scp cilium-install.yaml root@192.168.56.160:/root/
# === Master Node에서 ===
kubectl apply -f cilium-install.yaml
# 상태 확인
kubectl get pods -n kube-system
kubectl get nodes
# Master Node에서 실행
kubectl get nodes
kubectl get pods --all-namespaces
kubectl cluster-info
# Cilium 연결 확인
kubectl exec -n kube-system -it ds/cilium -- cilium status --verbose
# 클러스터 초기화 실패 시 리셋
sudo kubeadm reset -f
sudo rm -rf /etc/kubernetes
sudo rm -rf /var/lib/etcd
sudo systemctl restart containerd
sudo systemctl restart kubelet
# 로그 확인
sudo journalctl -xeu kubelet
sudo journalctl -xeu containerd
sudo journalctl -xeu docker
# Private Registry 연결 테스트
curl -X GET http://192.168.56.159:5000/v2/_catalog