IAM Role : iam-role-kubernetes-master-node
# iam-policy-kubernetes-master-node
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:CreateSecret",
"secretsmanager:GetSecretValue",
"secretsmanager:UpdateSecret",
"secretsmanager:DescribeSecret",
"secretsmanager:PutSecretValue",
"secretsmanager:DeleteSecret"
],
"Resource": "arn:aws:secretsmanager:*:*:secret:k8s/*"
},
{
"Effect": "Allow",
"Action": [
"secretsmanager:ListSecrets"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"ec2:DescribeInstances",
"ec2:DescribeTags",
"ec2:DescribeRegions"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"elasticloadbalancing:AddTags",
"elasticloadbalancing:CreateListener",
"elasticloadbalancing:CreateLoadBalancer",
"elasticloadbalancing:CreateRule",
"elasticloadbalancing:CreateTargetGroup",
"elasticloadbalancing:DeleteListener",
"elasticloadbalancing:DeleteLoadBalancer",
"elasticloadbalancing:DeleteRule",
"elasticloadbalancing:DeleteTargetGroup",
"elasticloadbalancing:DeregisterTargets",
"elasticloadbalancing:DescribeListeners",
"elasticloadbalancing:DescribeLoadBalancers",
"elasticloadbalancing:DescribeRules",
"elasticloadbalancing:DescribeTargetGroups",
"elasticloadbalancing:DescribeTargetHealth",
"elasticloadbalancing:ModifyListener",
"elasticloadbalancing:ModifyLoadBalancerAttributes",
"elasticloadbalancing:ModifyRule",
"elasticloadbalancing:ModifyTargetGroup",
"elasticloadbalancing:ModifyTargetGroupAttributes",
"elasticloadbalancing:RegisterTargets",
"elasticloadbalancing:SetIpAddressType",
"elasticloadbalancing:SetSecurityGroups",
"elasticloadbalancing:SetSubnets"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"ec2:CreateSecurityGroup",
"ec2:DeleteSecurityGroup",
"ec2:DescribeSecurityGroups",
"ec2:RevokeSecurityGroupIngress",
"ec2:AuthorizeSecurityGroupIngress"
],
"Resource": "*"
}
]
}
k8s-base-ami-setup.sh
#!/bin/bash
# 스크립트 실행 로그 파일 설정
LOG_FILE="/var/log/k8s-setup.log"
exec > >(tee -a ${LOG_FILE}) 2>&1
echo "======================================================"
echo "Kubernetes Base AMI 설치 스크립트 시작 - $(date)"
echo "======================================================"
# 설치 버전 변수 설정 - 쉽게 업데이트할 수 있도록 상단에 정의
CONTAINERD_VERSION="1.6.9"
RUNC_VERSION="1.1.3"
CNI_VERSION="1.1.1"
KUBERNETES_VERSION="1.28" # 메이저.마이너 버전만 지정
REGION="us-west-1" # 기본 리전 (스크립트 파라미터로 재정의 가능)
# 명령줄 파라미터 처리
while [[ $# -gt 0 ]]; do
key="$1"
case $key in
--region)
REGION="$2"
shift 2
;;
--containerd-version)
CONTAINERD_VERSION="$2"
shift 2
;;
--runc-version)
RUNC_VERSION="$2"
shift 2
;;
--cni-version)
CNI_VERSION="$2"
shift 2
;;
--k8s-version)
KUBERNETES_VERSION="$2"
shift 2
;;
*)
echo "알 수 없는 옵션: $1"
exit 1
;;
esac
done
# 루트 권한 확인
if [ "$(id -u)" -ne 0 ]; then
echo "이 스크립트는 root 권한으로 실행해야 합니다."
exit 1
fi
# 시스템 사양 확인
echo "시스템 사양 확인 중..."
CPU_COUNT=$(nproc)
MEM_TOTAL=$(free -m | awk '/^Mem:/{print $2}')
DISK_AVAIL=$(df -h / | awk 'NR==2 {print $4}' | sed 's/G//')
echo "CPU: $CPU_COUNT cores, Memory: $MEM_TOTAL MB, Disk available: ${DISK_AVAIL}GB"
if [ "$CPU_COUNT" -lt 2 ]; then
echo "경고: Kubernetes 노드에는 최소 2개의 CPU 코어가 권장됩니다."
fi
if [ "$MEM_TOTAL" -lt 2048 ]; then
echo "경고: Kubernetes 노드에는 최소 2GB 메모리가 권장됩니다."
fi
# 1. 시스템 업데이트
echo "[1/10] 시스템 업데이트 중..."
apt update || { echo "apt update 실패"; exit 1; }
apt upgrade -y || { echo "apt upgrade 실패"; exit 1; }
# 2. AWS CLI 설치
echo "[2/10] AWS CLI 설치 중..."
if ! command -v aws &> /dev/null; then
apt install -y awscli || { echo "AWS CLI 설치 실패"; exit 1; }
aws --version
echo "AWS CLI 설치 완료"
else
echo "AWS CLI가 이미 설치되어 있습니다."
aws --version
fi
# AWS 기본 리전 설정
echo "AWS 기본 리전을 $REGION으로 설정합니다."
mkdir -p /root/.aws
cat > /root/.aws/config << EOF
[default]
region = $REGION
output = json
EOF
# 3. 호스트명 설정 스크립트 준비
echo "[3/10] 호스트명 설정 스크립트 준비 중..."
cat > /usr/local/bin/set-hostname.sh << 'EOF'
#!/bin/bash
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
INSTANCE_ID=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/instance-id)
REGION=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/placement/region)
NAME_TAG=$(aws ec2 describe-tags --region $REGION --filters "Name=resource-id,Values=$INSTANCE_ID" "Name=key,Values=Name" --query "Tags[0].Value" --output text)
if [ -z "$NAME_TAG" ] || [ "$NAME_TAG" == "None" ]; then
echo "EC2 인스턴스에 Name 태그가 없습니다. 인스턴스 ID를 사용합니다."
NAME_TAG="k8s-node-$INSTANCE_ID"
fi
hostnamectl set-hostname "$NAME_TAG"
echo "호스트명이 '$NAME_TAG'로 설정되었습니다."
EOF
chmod +x /usr/local/bin/set-hostname.sh
echo "호스트명 설정 스크립트 생성 완료"
# EC2 인스턴스 시작 시 호스트명 자동 설정을 위한 서비스 등록
cat > /etc/systemd/system/set-hostname.service << EOF
[Unit]
Description=Set hostname from EC2 Name tag
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/set-hostname.sh
RemainAfterExit=true
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable set-hostname.service
# 호스트명 즉시 설정 (재부팅 없이)
echo "호스트명 설정 스크립트 즉시 실행 중..."
/usr/local/bin/set-hostname.sh
echo "호스트명 자동 설정 서비스 등록 및 즉시 적용 완료"
# 4. 스왑 비활성화
echo "[4/10] 스왑 비활성화 중..."
swapoff -a
sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
echo "스왑 비활성화 완료"
# 5. 커널 모듈 설정
echo "[5/10] 커널 모듈 설정 중..."
cat > /etc/modules-load.d/k8s.conf << EOF
overlay
br_netfilter
EOF
modprobe overlay || { echo "모듈 overlay 로드 실패"; }
modprobe br_netfilter || { echo "모듈 br_netfilter 로드 실패"; }
# 커널 파라미터 설정
cat > /etc/sysctl.d/k8s.conf << EOF
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sysctl --system || { echo "sysctl 설정 적용 실패"; }
echo "커널 모듈 및 파라미터 설정 완료"
# 6. containerd 설치
echo "[6/10] containerd 설치 중..."
apt install -y curl gnupg software-properties-common apt-transport-https ca-certificates || { echo "필수 패키지 설치 실패"; exit 1; }
# containerd 다운로드 및 설치
echo "containerd 버전 ${CONTAINERD_VERSION} 설치 중..."
wget -q https://github.com/containerd/containerd/releases/download/v${CONTAINERD_VERSION}/containerd-${CONTAINERD_VERSION}-linux-amd64.tar.gz || { echo "containerd 다운로드 실패"; exit 1; }
tar Cxzvf /usr/local containerd-${CONTAINERD_VERSION}-linux-amd64.tar.gz
rm containerd-${CONTAINERD_VERSION}-linux-amd64.tar.gz
# containerd 서비스 등록
mkdir -p /usr/local/lib/systemd/system
wget -q https://raw.githubusercontent.com/containerd/containerd/main/containerd.service -O /usr/local/lib/systemd/system/containerd.service || { echo "containerd 서비스 파일 다운로드 실패"; exit 1; }
systemctl daemon-reload
systemctl enable --now containerd || { echo "containerd 활성화 실패"; exit 1; }
echo "containerd 설치 완료"
# 7. runc 설치
echo "[7/10] runc 설치 중..."
wget -q https://github.com/opencontainers/runc/releases/download/v${RUNC_VERSION}/runc.amd64 || { echo "runc 다운로드 실패"; exit 1; }
install -m 755 runc.amd64 /usr/local/sbin/runc
rm runc.amd64
echo "runc 설치 완료"
# 8. CNI 플러그인 설치
echo "[8/10] CNI 플러그인 설치 중..."
mkdir -p /opt/cni/bin
wget -q https://github.com/containernetworking/plugins/releases/download/v${CNI_VERSION}/cni-plugins-linux-amd64-v${CNI_VERSION}.tgz || { echo "CNI 플러그인 다운로드 실패"; exit 1; }
tar Cxzvf /opt/cni/bin cni-plugins-linux-amd64-v${CNI_VERSION}.tgz
rm cni-plugins-linux-amd64-v${CNI_VERSION}.tgz
echo "CNI 플러그인 설치 완료"
# 9. containerd를 CRI 런타임으로 사용하기 위한 설정
echo "[9/10] containerd 설정 중..."
mkdir -p /etc/containerd
containerd config default | tee /etc/containerd/config.toml >/dev/null
sed -i 's/SystemdCgroup \= false/SystemdCgroup \= true/g' /etc/containerd/config.toml
# cgroup 드라이버 설정을 systemd로 변경
if grep -q 'SystemdCgroup = true' /etc/containerd/config.toml; then
echo "containerd의 cgroup 드라이버가 systemd로 설정되었습니다."
else
echo "오류: containerd의 cgroup 드라이버 설정에 실패했습니다."
exit 1
fi
# pause 이미지 저장소 설정 (선택적)
# sed -i "s|k8s.gcr.io/pause|registry.k8s.io/pause|g" /etc/containerd/config.toml
# containerd 재시작
systemctl restart containerd || { echo "containerd 재시작 실패"; exit 1; }
# containerd 설정 상태 확인
echo "containerd 상태 확인 중..."
if systemctl is-active --quiet containerd; then
echo "containerd가 정상적으로 실행 중입니다."
else
echo "containerd 서비스가 실행되지 않았습니다. 로그를 확인하세요."
journalctl -u containerd --no-pager -n 20
exit 1
fi
# 10. Kubernetes 패키지 설치
echo "[10/10] Kubernetes 패키지 설치 중..."
mkdir -p /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v${KUBERNETES_VERSION}/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg || { echo "Kubernetes 키 다운로드 실패"; exit 1; }
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v${KUBERNETES_VERSION}/deb/ /" | tee /etc/apt/sources.list.d/kubernetes.list
# 패키지 업데이트 및 설치
apt-get update || { echo "apt update 실패"; exit 1; }
apt-get install -y kubelet kubeadm kubectl || { echo "Kubernetes 패키지 설치 실패"; exit 1; }
apt-mark hold kubelet kubeadm kubectl
echo "Kubernetes 패키지 설치 완료"
# kubelet 설정 - 노드 IP 자동 감지
cat > /etc/default/kubelet << EOF
KUBELET_EXTRA_ARGS=--node-ip=$(hostname -I | awk '{print $1}')
EOF
# 환경 확인
echo "설치된 버전 확인:"
echo -n "containerd: "
containerd --version
echo -n "runc: "
runc --version
echo -n "kubectl: "
kubectl version --client
echo -n "kubeadm: "
kubeadm version
# 추가 설정: 방화벽 확인 및 구성
echo "방화벽 상태 확인 중..."
if command -v ufw &> /dev/null; then
ufw status
echo "Kubernetes에 필요한 포트 개방 중..."
# 컨트롤 플레인 노드에 필요한 포트
ufw allow 6443/tcp # Kubernetes API 서버
ufw allow 2379:2380/tcp # etcd 서버 클라이언트 API
ufw allow 10250/tcp # Kubelet API
ufw allow 10251/tcp # kube-scheduler
ufw allow 10252/tcp # kube-controller-manager
# 워커 노드에 필요한 포트
ufw allow 10250/tcp # Kubelet API
ufw allow 30000:32767/tcp # NodePort 서비스
fi
# 설치 완료 메시지
echo "======================================================"
echo "Kubernetes Base AMI 설치가 완료되었습니다 - $(date)"
echo "이 인스턴스로 AMI를 생성하여 Kubernetes 클러스터의 마스터 및 워커 노드로 사용할 수 있습니다."
echo "======================================================"
k8s-master-init.sh
#!/bin/bash
# Kubernetes 마스터 노드 초기화 스크립트
# 로그 파일 설정
LOG_FILE="/var/log/k8s-master-init.log"
exec > >(tee -a ${LOG_FILE}) 2>&1
echo "======================================================"
echo "Kubernetes 마스터 노드 초기화 스크립트 시작 - $(date)"
echo "======================================================"
# 변수 설정
POD_CIDR="10.244.0.0/16" # 파드 네트워크 CIDR
SERVICE_CIDR="10.96.0.0/12" # 서비스 네트워크 CIDR
API_SERVER_PORT="6443" # API 서버 포트
SECRET_NAME="k8s/join" # AWS Secrets Manager 시크릿 이름
REGION="us-west-1" # AWS 리전
# 사전 검증 단계
check_prerequisites() {
echo "[1/7] 사전 요구사항 검증 중..."
# 시스템 자원 검증
CPU_COUNT=$(nproc)
MEM_TOTAL=$(free -m | awk '/^Mem:/{print $2}')
DISK_AVAIL=$(df -h / | awk 'NR==2 {print $4}' | sed 's/G//')
echo "CPU: $CPU_COUNT cores, Memory: $MEM_TOTAL MB, Disk available: ${DISK_AVAIL}GB"
if [ "$CPU_COUNT" -lt 2 ]; then
echo "경고: 마스터 노드에는 최소 2개의 CPU 코어가 권장됩니다."
fi
if [ "$MEM_TOTAL" -lt 2048 ]; then
echo "경고: 마스터 노드에는 최소 2GB 메모리가 권장됩니다."
fi
# 필수 포트 확인
for port in $API_SERVER_PORT 10250 10251 10252 2379 2380; do
if lsof -i:$port > /dev/null 2>&1; then
echo "경고: 포트 $port가 이미 사용 중입니다. 확인이 필요합니다."
fi
done
# containerd 상태 확인
if ! systemctl is-active --quiet containerd; then
echo "containerd가 실행 중이지 않습니다. 시작합니다."
systemctl start containerd
sleep 5
if ! systemctl is-active --quiet containerd; then
echo "오류: containerd를 시작할 수 없습니다. 로그를 확인하세요."
exit 1
fi
fi
# AWS CLI 확인
if ! command -v aws &> /dev/null; then
echo "AWS CLI가 설치되어 있지 않습니다. 설치를 진행합니다."
apt-get update && apt-get install -y awscli
fi
# AWS IAM 권한 확인
if ! aws secretsmanager get-random-password --password-length 4 --region $REGION &> /dev/null; then
echo "경고: AWS Secrets Manager 접근 권한이 없거나 구성이 잘못되었습니다."
echo "IAM 역할 및 권한을 확인하세요."
fi
echo "사전 요구사항 검증 완료"
}
# kubeadm init 실행
initialize_master() {
echo "[2/7] 마스터 노드 초기화 중..."
# kubeadm init 명령 실행
kubeadm init \
--pod-network-cidr=$POD_CIDR \
--service-cidr=$SERVICE_CIDR \
--upload-certs \
--v=5
# 초기화 결과 확인
if [ $? -ne 0 ]; then
echo "오류: kubeadm init 명령이 실패했습니다. 로그를 확인하세요."
exit 1
fi
echo "마스터 노드 초기화 완료"
}
# kubeconfig 설정
setup_kubeconfig() {
echo "[3/7] kubeconfig 설정 중..."
# 관리자 계정용 kubeconfig 설정
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config
# 환경 변수 설정
echo 'export KUBECONFIG=/etc/kubernetes/admin.conf' >> /etc/profile.d/k8s.sh
chmod +x /etc/profile.d/k8s.sh
# 일반 사용자 (ubuntu)에게도 kubeconfig 설정
if id "ubuntu" &>/dev/null; then
mkdir -p /home/ubuntu/.kube
cp -i /etc/kubernetes/admin.conf /home/ubuntu/.kube/config
chown -R ubuntu:ubuntu /home/ubuntu/.kube
echo "ubuntu 사용자를 위한 kubeconfig 설정 완료"
fi
echo "kubeconfig 설정 완료"
}
# 네트워크 솔루션 설치 (Calico)
install_network_solution() {
echo "[4/7] 네트워크 솔루션 설치 중..."
# Calico 네트워크 솔루션 설치
kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml
echo "네트워크 솔루션 설치 요청 완료"
# 네트워크 솔루션 설치 확인
echo "네트워크 파드 실행 상태 확인 중..."
attempt=0
max_attempts=30
while [ $attempt -lt $max_attempts ]; do
calico_pods_ready=$(kubectl get pods -n kube-system -l k8s-app=calico-node -o jsonpath='{.items[*].status.containerStatuses[0].ready}' | grep -o "true" | wc -l)
calico_pods_total=$(kubectl get pods -n kube-system -l k8s-app=calico-node --no-headers | wc -l)
if [ "$calico_pods_ready" -eq "$calico_pods_total" ] && [ "$calico_pods_total" -gt 0 ]; then
echo "모든 Calico 네트워크 파드가 준비되었습니다."
break
fi
echo "Calico 네트워크 파드 준비 중... ($calico_pods_ready/$calico_pods_total) 시도: $attempt/$max_attempts"
attempt=$((attempt+1))
sleep 10
done
if [ $attempt -eq $max_attempts ]; then
echo "경고: Calico 네트워크 파드가 시간 내에 준비되지 않았습니다."
fi
echo "네트워크 솔루션 설치 완료"
}
# 조인 정보 추출 및 AWS Secrets Manager 저장
save_join_info() {
echo "[5/7] 클러스터 조인 정보 저장 중..."
# 조인 토큰 추출
JOIN_COMMAND=$(kubeadm token create --print-join-command)
if [ -z "$JOIN_COMMAND" ]; then
echo "오류: 조인 명령어를 생성할 수 없습니다."
exit 1
fi
# 마스터 노드 IP 주소 가져오기
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
MASTER_PRIVATE_IP=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/local-ipv4)
if [ -z "$MASTER_PRIVATE_IP" ]; then
echo "경고: EC2 메타데이터에서 IP 주소를 가져올 수 없습니다."
MASTER_PRIVATE_IP=$(hostname -I | awk '{print $1}')
echo "대체 방법으로 IP 주소를 가져왔습니다: $MASTER_PRIVATE_IP"
fi
# AWS Secrets Manager 시크릿 JSON 생성
SECRET_JSON=$(cat <<EOF
{
"join": "$JOIN_COMMAND",
"master_private_ip": "$MASTER_PRIVATE_IP",
"created_at": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
}
EOF
)
# AWS Secrets Manager에 정보 저장
echo "AWS Secrets Manager에 정보 저장 중..."
# 시크릿이 이미 존재하는지 확인
if aws secretsmanager describe-secret --secret-id "$SECRET_NAME" --region "$REGION" &> /dev/null; then
# 시크릿 업데이트
aws secretsmanager update-secret \
--secret-id "$SECRET_NAME" \
--secret-string "$SECRET_JSON" \
--region "$REGION"
else
# 새 시크릿 생성
aws secretsmanager create-secret \
--name "$SECRET_NAME" \
--description "Kubernetes 클러스터 조인 정보" \
--secret-string "$SECRET_JSON" \
--region "$REGION"
fi
if [ $? -ne 0 ]; then
echo "경고: AWS Secrets Manager에 정보를 저장하는 데 실패했습니다."
echo "조인 정보: $JOIN_COMMAND"
echo "마스터 IP: $MASTER_PRIVATE_IP"
else
echo "AWS Secrets Manager에 정보가 성공적으로 저장되었습니다."
fi
echo "클러스터 조인 정보 저장 완료"
}
# 클러스터 상태 검증
validate_cluster() {
echo "[6/7] 클러스터 상태 검증 중..."
# 필수 시스템 파드 확인
echo "시스템 파드 상태 확인 중..."
kubectl get pods -n kube-system
# CoreDNS 파드 확인
coredns_pods_ready=$(kubectl get pods -n kube-system -l k8s-app=kube-dns -o jsonpath='{.items[*].status.containerStatuses[0].ready}' | grep -o "true" | wc -l)
coredns_pods_total=$(kubectl get pods -n kube-system -l k8s-app=kube-dns --no-headers | wc -l)
if [ "$coredns_pods_ready" -eq "$coredns_pods_total" ] && [ "$coredns_pods_total" -gt 0 ]; then
echo "CoreDNS 파드가 정상적으로 동작 중입니다."
else
echo "경고: CoreDNS 파드가 아직 준비되지 않았습니다."
fi
# 노드 상태 확인
node_status=$(kubectl get nodes -o jsonpath='{.items[0].status.conditions[?(@.type=="Ready")].status}')
if [ "$node_status" = "True" ]; then
echo "마스터 노드가 Ready 상태입니다."
else
echo "경고: 마스터 노드가 아직 Ready 상태가 아닙니다."
fi
# 간단한 테스트 파드 배포
echo "테스트 파드 배포 중..."
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: test-pod
labels:
app: test
spec:
containers:
- name: nginx
image: nginx:latest
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
EOF
# 테스트 파드 상태 확인
attempt=0
max_attempts=10
while [ $attempt -lt $max_attempts ]; do
pod_status=$(kubectl get pod test-pod -o jsonpath='{.status.phase}')
if [ "$pod_status" = "Running" ]; then
echo "테스트 파드가 정상적으로 실행 중입니다."
break
fi
echo "테스트 파드 준비 중... 상태: $pod_status, 시도: $attempt/$max_attempts"
attempt=$((attempt+1))
sleep 5
done
if [ $attempt -eq $max_attempts ]; then
echo "경고: 테스트 파드가 시간 내에 Running 상태가 되지 않았습니다."
fi
# 테스트 파드 삭제
kubectl delete pod test-pod
echo "클러스터 상태 검증 완료"
}
# 마스터 노드 요약 정보 표시
print_summary() {
echo "[7/7] 마스터 노드 설정 요약:"
# Kubernetes 버전 정보
echo "Kubernetes 버전: $(kubectl version --short | grep Server)"
# 노드 상태
echo "노드 상태:"
kubectl get nodes
# 파드 상태
echo "시스템 파드 상태:"
kubectl get pods -n kube-system
# 조인 정보
TOKEN=$(kubeadm token list | grep -v EXPIRES | head -n 1 | awk '{print $1}')
HASH=$(openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //')
MASTER_IP=$(hostname -I | awk '{print $1}')
echo "워커 노드 조인 정보:"
echo "kubeadm join ${MASTER_IP}:${API_SERVER_PORT} --token ${TOKEN} --discovery-token-ca-cert-hash sha256:${HASH}"
echo "AWS Secrets Manager 정보:"
echo "Secret Name: $SECRET_NAME, Region: $REGION"
# 설치된 CNI 플러그인
echo "네트워크 솔루션: Calico"
}
# 메인 로직 시작
echo "Kubernetes 마스터 노드 초기화 프로세스 시작..."
# 1. 사전 검증 단계
check_prerequisites
# 2. kubeadm init 실행
initialize_master
# 3. kubeconfig 설정
setup_kubeconfig
# 4. 네트워크 솔루션 설치
install_network_solution
# 5. 조인 정보 추출 및 AWS Secrets Manager 저장
save_join_info
# 6. 클러스터 상태 검증
validate_cluster
# 7. 요약 정보 표시
print_summary
echo "======================================================"
echo "Kubernetes 마스터 노드 초기화 스크립트 완료 - $(date)"
echo "======================================================"
k8s-worker-init.sh
#!/bin/bash
# Kubernetes 워커 노드 자동 조인 스크립트 (JSON 파싱 개선 버전)
# 로그 파일 설정
LOG_FILE="/var/log/k8s-join.log"
exec > >(tee -a ${LOG_FILE}) 2>&1
echo "======================================================"
echo "Kubernetes 워커 노드 자동 조인 스크립트 시작 - $(date)"
echo "======================================================"
# 변수 설정
SECRET_NAME="k8s/join"
REGION="us-west-1"
MAX_RETRIES=5
RETRY_INTERVAL=10 # 초 단위
NODE_LABELS="node-role.kubernetes.io/worker=true" # 노드 라벨
# 명령줄 파라미터 처리
while [[ $# -gt 0 ]]; do
key="$1"
case $key in
--region)
REGION="$2"
shift 2
;;
--secret-name)
SECRET_NAME="$2"
shift 2
;;
--labels)
NODE_LABELS="$2"
shift 2
;;
--max-retries)
MAX_RETRIES="$2"
shift 2
;;
--retry-interval)
RETRY_INTERVAL="$2"
shift 2
;;
*)
echo "알 수 없는 옵션: $1"
exit 1
;;
esac
done
# 루트 권한 확인
if [ "$(id -u)" -ne 0 ]; then
echo "이 스크립트는 root 권한으로 실행해야 합니다."
exit 1
fi
# AWS CLI 확인 및 설치
check_aws_cli() {
echo "[1/6] AWS CLI 확인 중..."
if ! command -v aws &> /dev/null; then
echo "AWS CLI가 설치되어 있지 않습니다. 설치를 진행합니다."
apt-get update && apt-get install -y awscli
if [ $? -ne 0 ]; then
echo "오류: AWS CLI 설치에 실패했습니다."
return 1
fi
fi
# AWS 기본 리전 설정
mkdir -p /root/.aws
cat > /root/.aws/config << EOF
[default]
region = $REGION
output = json
EOF
echo "AWS CLI가 설치되어 있습니다. 버전: $(aws --version)"
return 0
}
# 시스템 및 노드 준비 상태 확인
check_node_readiness() {
echo "[2/6] 노드 준비 상태 확인 중..."
# 시스템 사양 확인
CPU_COUNT=$(nproc)
MEM_TOTAL=$(free -m | awk '/^Mem:/{print $2}')
DISK_AVAIL=$(df -h / | awk 'NR==2 {print $4}' | sed 's/G//')
echo "CPU: $CPU_COUNT cores, Memory: $MEM_TOTAL MB, Disk available: ${DISK_AVAIL}GB"
if [ "$CPU_COUNT" -lt 2 ]; then
echo "경고: Kubernetes 노드에는 최소 2개의 CPU 코어가 권장됩니다."
fi
if [ "$MEM_TOTAL" -lt 2048 ]; then
echo "경고: Kubernetes 노드에는 최소 2GB 메모리가 권장됩니다."
fi
# containerd 상태 확인
if ! systemctl is-active --quiet containerd; then
echo "containerd가 실행 중이지 않습니다. 시작합니다."
systemctl start containerd
sleep 5
if ! systemctl is-active --quiet containerd; then
echo "오류: containerd를 시작할 수 없습니다."
return 1
fi
fi
# kubelet 상태 확인
if ! systemctl is-active --quiet kubelet; then
echo "kubelet이 실행 중이지 않습니다. 시작합니다."
systemctl start kubelet
sleep 5
fi
# 필수 포트 확인
for port in 10250 30000-32767; do
if echo $port | grep -q "-"; then
# 포트 범위인 경우
start_port=$(echo $port | cut -d'-' -f1)
end_port=$(echo $port | cut -d'-' -f2)
echo "포트 범위 $start_port-$end_port는 자동으로 검증할 수 없습니다."
else
# 단일 포트인 경우
if lsof -i:$port > /dev/null 2>&1; then
echo "경고: 포트 $port가 이미 사용 중입니다. Kubernetes 구성 요소와 충돌할 수 있습니다."
fi
fi
done
echo "노드가 준비되었습니다."
return 0
}
# AWS Secrets Manager에서 마스터 노드 정보 가져오기 (개선된 JSON 파싱)
get_master_info() {
echo "[3/6] AWS Secrets Manager에서 마스터 노드 정보 가져오기..."
local attempt=1
while [ $attempt -le $MAX_RETRIES ]; do
echo "시도 $attempt/$MAX_RETRIES..."
# Secrets Manager에서 시크릿 값 가져오기
local secret_value=$(aws secretsmanager get-secret-value \
--region ${REGION} \
--secret-id ${SECRET_NAME} \
--query "SecretString" \
--output text 2>/dev/null)
# 명령어 실행 상태 확인
if [ $? -ne 0 ] || [ -z "$secret_value" ]; then
echo "시크릿 값을 가져오는 데 실패했습니다. $RETRY_INTERVAL초 후 재시도합니다."
sleep $RETRY_INTERVAL
attempt=$((attempt+1))
continue
fi
echo "시크릿 값을 성공적으로 가져왔습니다."
# jq가 있는지 확인하고, 없으면 설치
if ! command -v jq &> /dev/null; then
echo "jq를 설치합니다..."
apt-get update && apt-get install -y jq
fi
# jq를 사용하여 JSON 파싱
if command -v jq &> /dev/null; then
echo "jq를 사용하여 JSON 파싱 중..."
MASTER_IP=$(echo "$secret_value" | jq -r '.master_private_ip')
JOIN_COMMAND=$(echo "$secret_value" | jq -r '.join')
CREATED_AT=$(echo "$secret_value" | jq -r '.created_at')
else
echo "jq를 사용할 수 없어 대체 방법으로 파싱 중..."
# 이스케이프된 줄바꿈 제거 후 파싱
secret_value=$(echo "$secret_value" | tr -d '\n')
MASTER_IP=$(echo "$secret_value" | grep -o '"master_private_ip": *"[^"]*"' | grep -o '"[^"]*"$' | tr -d '"')
JOIN_COMMAND=$(echo "$secret_value" | grep -o '"join": *"[^"]*"' | grep -o '"[^"]*"$' | tr -d '"')
CREATED_AT=$(echo "$secret_value" | grep -o '"created_at": *"[^"]*"' | grep -o '"[^"]*"$' | tr -d '"')
fi
if [ -n "$MASTER_IP" ] && [ -n "$JOIN_COMMAND" ]; then
echo "마스터 IP 주소: $MASTER_IP"
echo "조인 명령어: $JOIN_COMMAND"
echo "조인 명령어 생성 시간: $CREATED_AT"
# 토큰 생성 시간과 현재 시간 비교 (24시간 이상이면 경고)
local created_timestamp=$(date -d "$CREATED_AT" +%s 2>/dev/null)
local current_timestamp=$(date +%s)
if [ -n "$created_timestamp" ] && [ $((current_timestamp - created_timestamp)) -gt 86400 ]; then
echo "경고: 조인 토큰이 24시간 이상 지났습니다. 만료되었을 수 있습니다."
fi
return 0
else
echo "마스터 노드 정보를 파싱할 수 없습니다. 원본 JSON: $secret_value"
sleep $RETRY_INTERVAL
attempt=$((attempt+1))
fi
done
echo "오류: 최대 재시도 횟수($MAX_RETRIES)를 초과했습니다."
return 1
}
# 호스트 이름 및 /etc/hosts 설정
setup_hostname_and_hosts() {
echo "[4/6] 호스트 이름 및 /etc/hosts 설정 중..."
# 호스트 이름 설정 (이미 set-hostname.service에서 처리되었을 수 있음)
current_hostname=$(hostname)
echo "현재 호스트 이름: $current_hostname"
if [[ "$current_hostname" == "ip-"* || "$current_hostname" == "localhost" ]]; then
echo "기본 호스트 이름이 감지되었습니다. set-hostname.sh 스크립트를 실행합니다."
if [ -x /usr/local/bin/set-hostname.sh ]; then
/usr/local/bin/set-hostname.sh
echo "호스트 이름이 업데이트되었습니다: $(hostname)"
else
echo "경고: 호스트 이름 설정 스크립트를 찾을 수 없습니다."
fi
fi
# /etc/hosts 파일에 마스터 노드 추가 또는 업데이트
if [ -n "$MASTER_IP" ]; then
if grep -q "k8s-master" /etc/hosts; then
echo "호스트 파일에 k8s-master 항목이 이미 있습니다. 업데이트합니다."
sed -i "/k8s-master/c\\$MASTER_IP k8s-master" /etc/hosts
else
echo "호스트 파일에 k8s-master 항목을 추가합니다."
echo "$MASTER_IP k8s-master" >> /etc/hosts
fi
# 호스트 파일 확인
echo "현재 /etc/hosts 파일:"
cat /etc/hosts | grep k8s-master
else
echo "경고: 마스터 IP가 없어 /etc/hosts 파일을 업데이트할 수 없습니다."
return 1
fi
return 0
}
# 클러스터 조인
join_cluster() {
echo "[5/6] Kubernetes 클러스터 조인 시도 중..."
if [ -z "$JOIN_COMMAND" ]; then
echo "오류: 조인 명령어가 없습니다."
return 1
fi
# 이미 조인되어 있는지 확인
if [ -f "/etc/kubernetes/kubelet.conf" ]; then
echo "경고: 이 노드는 이미 Kubernetes 클러스터에 조인되어 있는 것 같습니다."
echo "재조인하려면 먼저 'kubeadm reset'을 실행하세요."
return 0
fi
echo "실행할 조인 명령어: $JOIN_COMMAND"
# 노드 라벨 추가 (--node-labels는 kubelet에 전달됨)
local node_labels_arg=""
if [ -n "$NODE_LABELS" ]; then
node_labels_arg="--node-labels=$NODE_LABELS"
fi
# 조인 명령어에 kubelet 인수 추가
local join_cmd_with_labels="$JOIN_COMMAND"
if [ -n "$node_labels_arg" ]; then
if [[ "$JOIN_COMMAND" == *"--discovery"* ]]; then
# --discovery 인수 뒤에 노드 라벨 추가
join_cmd_with_labels="${JOIN_COMMAND} $node_labels_arg"
else
echo "경고: 조인 명령어 형식이 예상과 다릅니다. 노드 라벨을 추가하지 않습니다."
fi
fi
# 조인 명령어 실행
local attempt=1
while [ $attempt -le $MAX_RETRIES ]; do
echo "조인 시도 $attempt/$MAX_RETRIES..."
eval "$join_cmd_with_labels"
if [ $? -eq 0 ]; then
echo "클러스터 조인 성공!"
return 0
else
echo "조인 실패. $RETRY_INTERVAL초 후 재시도합니다."
sleep $RETRY_INTERVAL
attempt=$((attempt+1))
fi
done
echo "오류: 최대 재시도 횟수($MAX_RETRIES)를 초과했습니다."
return 1
}
# 클러스터 조인 상태 확인
verify_join_status() {
echo "[6/6] 클러스터 조인 상태 확인 중..."
# kubelet 설정 파일 확인
if [ ! -f "/etc/kubernetes/kubelet.conf" ]; then
echo "오류: kubelet.conf 파일이 없습니다. 클러스터 조인에 실패했을 수 있습니다."
return 1
fi
# kubelet 서비스 상태 확인
if ! systemctl is-active --quiet kubelet; then
echo "오류: kubelet 서비스가 실행되지 않고 있습니다."
systemctl status kubelet --no-pager
return 1
fi
echo "kubelet 서비스가 정상적으로 실행 중입니다."
# CNI 네트워크 인터페이스 확인
if ! ip a | grep -q "cni"; then
echo "경고: CNI 네트워크 인터페이스가 아직 설정되지 않았습니다."
echo "이는 정상일 수 있으며, 클러스터 네트워킹이 완전히 설정되는 데 몇 분 정도 걸릴 수 있습니다."
else
echo "CNI 네트워크 인터페이스가 설정되었습니다."
fi
echo "노드 정보 요약:"
echo "호스트 이름: $(hostname)"
echo "IP 주소: $(hostname -I | awk '{print $1}')"
echo "Kubernetes 버전: $(kubelet --version)"
echo "조인 프로세스가 완료되었습니다. 마스터 노드에서 'kubectl get nodes'를 실행하여 이 노드가 표시되는지 확인하세요."
return 0
}
# 메인 로직 시작
echo "Kubernetes 워커 노드 자동 조인 프로세스 시작..."
# 1. AWS CLI 확인
check_aws_cli || { echo "오류: AWS CLI 설정에 실패했습니다."; exit 1; }
# 2. 노드 준비 상태 확인
check_node_readiness || { echo "오류: 노드 준비 상태 확인에 실패했습니다."; exit 1; }
# 3. 마스터 노드 정보 가져오기
get_master_info || { echo "오류: 마스터 노드 정보를 가져오는 데 실패했습니다."; exit 1; }
# 4. 호스트 이름 및 /etc/hosts 설정
setup_hostname_and_hosts || { echo "오류: 호스트 설정에 실패했습니다."; exit 1; }
# 5. 클러스터 조인
join_cluster || { echo "오류: 클러스터 조인에 실패했습니다."; exit 1; }
# 6. 조인 상태 확인
verify_join_status || { echo "경고: 조인 상태 확인에 실패했습니다."; }
echo "======================================================"
echo "Kubernetes 워커 노드 자동 조인 스크립트 완료 - $(date)"
echo "======================================================"