본문 바로가기
IT/Container

Hyper-V 에 VM을 만들어 kubernetes 클러스터 구성하기

by blogger 2022. 5. 22.

Quick start

systemctl stop firewalld         ; \
systemctl disable firewalld      ; \
systemctl mask --now firewalld   ; \
firewall-cmd --state             ; \
sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config ; \
setenforce 0  ; \
yum install -y yum-utils ; \
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo  ; \
yum install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin  ; \
yum install -y net-tools vim wget telnet ; \
adduser docker -g docker ; \
usermod -G docker docker ; \
systemctl start docker ; \
systemctl enable docker ; \
echo "192.168.0.160    haproxy" >> /etc/hosts ;\ 
echo "192.168.0.161    cluster1" >> /etc/hosts ;\ 
echo "192.168.0.162    cluster2" >> /etc/hosts ;\ 
echo "192.168.0.163    cluster3" >> /etc/hosts ;\ 
echo "192.168.0.160    docker-registry.private.net" >> /etc/hosts ;\
cat <<EOF | tee /etc/docker/daemon.json
{
    "exec-opts": ["native.cgroupdriver=systemd"],
    "log-driver": "json-file",
    "log-opts": {
        "max-size": "10m"
    },
    "storage-driver": "overlay2",
    "max-concurrent-downloads": 7,
    "insecure-registries": ["docker-registry.private.net:5000"]
}
EOF ; \
mkdir /etc/modules-load.d/k8s ; \
echo br_netfilter >> /etc/modules-load.d/k8s/conf  ; \
touch /etc/sysctl.d/k8s.conf ; \
echo "net.bridge.bridge-nf-call-ip6tables = 1" >> /etc/sysctl.d/k8s.conf
echo "net.bridge.bridge-nf-call-iptables = 1" >> /etc/sysctl.d/k8s.conf
echo "vm.swappiness = 0" >> /etc/sysctl.d/k8s.conf
modprobe br_netfilter ; \
sysctl -f ; \
reboot

-------------------- initial container settings -----------------

cat <<EOF | tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-\$basearch
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
exclude=kubelet kubeadm kubectl
EOF ; \
yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes ; \
systemctl enable --now kubelet ; \
yum install -y yum-versionlock ; \
yum versionlock add kubelet kubeadm kubectl ; \
sed -i 's/disabled_plugins = .*/#disabled_plugins = []/'ot@vm1 ~]# vim /etc/containerd/config.toml ; \
systemctl restart containerd ; \

------------------------------------- installed kubeadm -----------------------------

kubeadm init --control-plane-endpoint 192.168.0.10:6443 --upload-certs --pod-network-cidr=10.10.0.0/16 >> result.txt


------------------------------------- kubeadm init --control-plane-endpoint -------------

mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config

1. VM 생성하고 기본 설정

1.1. Linux (CentOS 7) 이미지 다운로드

1.3. ethernet settings

default gateway 설정

# cat /etc/sysconfig/network
GATEWAYDEV=eth0
GATEWAY=192.168.0.1
NETWORKING=yes

eth0 고정 아이피 설정

# static IP address on CentOS 7 or RHEL 7#
HWADDR=00:08:A2:0A:BA:B8
TYPE=Ethernet
BOOTPROTO=none
# Server IP #
IPADDR=192.168.0.10
# Subnet #
PREFIX=24
# Set default gateway IP #
GATEWAY=192.168.0.1
# Set dns servers #
DNS1=192.168.0.254
DNS2=8.8.8.8
DNS3=8.8.4.4
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
# Disable ipv6 #
IPV6INIT=no
NAME=eth0
# This is system specific and can be created using 'uuidgen eth0' command #
UUID=41171a6f-bce1-44de-8a6e-cf5e782f8bd6
DEVICE=eth0
ONBOOT=yes

참조 : https://www.cyberciti.biz/faq/howto-setting-rhel7-centos-7-static-ip-configuration/

1.4. selinux 비활성화

sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
setenforce 0
reboot  # 재부팅해야 비활성화 상태로 됨

1.5. firewall 비활성화

서비스 포트 별 방화벽 규칙을 설정할 것이 아니라면 firewall을 해제해 버리자.
(단 클러스터 앞단에 방화벽은 별도로 구성/관리되어야 한다!)

systemctl stop firewalld           # 현재 기동중인 방화벽 중지
systemctl disable firewalld        # 시스템 기동시 방화벽 기동 비활성화
systemctl mask --now firewalld     # 다른 서비스에서 방화벽 시작 방지
firewall-cmd --state              # 방화벽 서비스 상태 확인

1.6. /etc/hosts 설정

192.168.0.11    cluster1
192.168.0.12    cluster2
192.168.0.13    cluster3

1.7. ssh key 생성 및 cluster 간 개인키 복사

ssh-keygen -t rsa
ssh-copy-id cluster2
ssh-copy-id cluster3

1.8. ansible 설치 (클러스터 호스트 일괄 제어)

yum install epel-release -y
yum install ansible wget -y

ansible inventory 설정 (/etc/hosts 설정 host 사용)

cat <<EOF | tee /etc/ansible/hosts

[master]
cluster1
cluster2
cluster3

[all:vars]
ansible_user=root
ansible_connection=ssh
ansible_port=22

EOF

ansible hosts 복사

ansible -m copy -a "src=/etc/ansible/hosts dest=/etc/ansible/hosts" all

2. docker 설치

ansible all -m shell -a "yum install -y yum-utils"
ansible all -m shell -a "yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo"
ansible all -m shell -a "yum install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin"

2.1. 사용자 계정 생성

ansible all -m shell -a "adduser docker -g docker"
ansible all -m shell -a "usermod -G docker docker"

2.2. 도커 서비스 등록 및 실행

ansible all -m shell -a "systemctl start docker"
ansible all -m shell -a "systemctl enable docker"

2.3. 도커 데이터 디렉터리 변경

systemctl stop docker docker.socket
mkdir -p /data
mv /var/lib/docker /data/docker
ln -s /data/docker /var/lib/docker

2.4. 도커 설정 변경

cat <<EOF | tee /etc/docker/daemon.json
{
    "exec-opts": ["native.cgroupdriver=systemd"],
    "log-driver": "json-file",
    "log-opts": {
        "max-size": "10m"
    },
    "storage-driver": "overlay2",
    "max-concurrent-downloads": 7,
    "insecure-registries": ["docker-registry.private.net:5000"]
}
EOF

3. Kubernetes 설치

공식문서 : https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm

3.1. iptables가 브리지된 트래픽을 보게 하기

bridge netfilter 설정을 해주어야 pod 끼리 통신이 가능하게 된다.

/etc/modules-load.d/k8s/conf

mkdir /etc/modules-load.d/k8s
echo br_netfilter >> /etc/modules-load.d/k8s/conf

/etc/sysctl.d/k8s.conf

net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1

적용

ansible -m shell -ba "modprobe br_netfilter" all
ansible -m shell -ba "sysctl -f" all

확인

ansible all -m shell -ba "lsmod | grep br_netfilter"
ansible all -m shell -ba "sysctl -a | egrep 'net.bridge.bridge-nf-call-iptables|net.bridge.bridge-nf-call-ip6tables'"

3.2. swap off

kubernetes는 메모리 스왑을 고려하지 않고 설계되었다고 하며, 비활성화를 강력 권고하고있다.

sysctl vm.swappiness=0
swapoff -v /dev/mapper/centos-swap

removing mount swap file from /etc/fstab

3.3. 쿠퍼네티스 레포지터리 설정

cat <<EOF | tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-\$basearch
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
exclude=kubelet kubeadm kubectl
EOF

3.4. kubeadmin kubectl 설치

ansible all -m copy -a "src=/etc/yum.repos.d/kubernetes.repo dest=/etc/yum.repos.d/kubernetes.repo"
ansible all -m shell -a "yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes"
ansible all -m shell -a "systemctl enable --now kubelet"

version 고정

yum install -y yum-versionlock
yum versionlock add kubelet kubeadm kubectl

cgroup 확인

docker 와 kubernetes 가 동일한 cgroup driver를 사용하는지 확인한다.
docker의 경우 /etc/docker/daemon.json 에서 systemd 사용을 기 적용하였고, docker info 에서 cgroup 정보를 확인할 수 있다.
만일 cgroup이 systemd가 아닐 경우 k8s 공식 문서 및 구글링을 통해 변경하도록 하자.

3.5. kubeadmin을 통한 클러스터 구성

kubeadm init --control-plane-endpoint 192.168.0.10:6443 --upload-certs --pod-network-cidr=10.10.0.0/16 >> result.txt
# kubeadm init --control-plane-endpoint 192.168.0.10:6443 --upload-certs --pod-network-cidr=10.10.0.0/16 --image-repository=docker-registry.private.com >> result.txt
  • --control-plane-endpoint : 옵션은 엔드포인트를 지정합니다. HA 구성에 사용된 HAProxy 주소
  • --pod-network-cidr : pod 네트워크 IP 사용 범위
  • --upload-certs : 인증서를 생성 및 사용
  • --image-repository : 사용할 registry를 지정 (아래 5절 참조. 폐쇄망에서 설치시 registry 지정)
  • --kubernetes-version : 설치될 kubernetes 클러스터의 버전을 지정

[ERROR CRI]: container runtime is not running

/etc/containerd/config.toml 파일에서 다음 내용을 주석처리(제거)

disabled_plugins = ["cri"]

containerd 재시작

systemctl restart containerd

3.6. kubectl 설정

kubectl 사용을 위해 config 파일을 복사한다.

mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config

# 구성확인을 위해 다음 명령어 동작을 확인한다.
kubectl get node

3.7. calico 설치 (vxLAN)

Kubernetes Network 환경을 위한 Calico 설치를 한다.

wget https://docs.projectcalico.org/manifests/calico.yaml
# calico.yaml 파일 편집
calico-node > DaemonSet > container 의 env 에 다음 추가
- name: IP_AUTODETECTION_METHOD
  value: "interface=eth0"

# 다음 부분 찾아 변경 (k8s 클러스터 설치시 지정한 ip range)
- name: CALICO_IPV4POOL_CIDR
  value: "10.10.0.0/16"

# 설치
kubectl apply -f calico.yaml

설치 완료 후 잠시 기다린 다음 node 정보를 확인하여 STATUS 가 ready 상태로 변경되었는지 확인한다.

kubectl get nodes
kubectl get pod -n kube-system    # 모두 running이지 확인

3.8. kubeadmin HA 구성 (VM2, VM3에서 kubeadmin join)

Kubeadmin HA 구성을 위한 각각의 호스트에서 앞서 진행한 3.5. 에서 kubeadmin init 의 아웃풋으로 생성된 result.txt 파일에 kubeadmin join 을 위한 토큰 정보가 포함된 명령어를 실행한다.

 kubeadm join 192.168.0.10:6443 --token xxxxxxxxxxxxxxxxxxxxxxxxx \
        --discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxx \
        --control-plane --certificate-key xxxxxxxxxxxxxxxxxxxxxxx

kubectl 사용을 위해 다음을 각각의 master 노드에서 실행한다

mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config

# 구성확인을 위해 다음 명령어 동작을 확인한다.
kubectl get node

calico-kube-control Pending

[root@vm1 ~]# kubectl get pod -n kube-system
NAME                                         READY   STATUS    RESTARTS   AGE
calico-kube-controllers-56cdb7c587-ndxrk     0/1     Pending   0          7h56m
calico-node-8bxfg                            1/1     Running   0          7h56m
coredns-6d4b75cb6d-5kg54                     1/1     Running   0          8h
coredns-6d4b75cb6d-95lk9                     1/1     Running   0          8h
etcd-vm1.test.undefined                      1/1     Running   0          8h
kube-apiserver-vm1.test.undefined            1/1     Running   0          8h
kube-controller-manager-vm1.test.undefined   1/1     Running   0          8h
kube-proxy-h6zbd                             1/1     Running   0          8h
kube-scheduler-vm1.test.undefined            1/1     Running   0          8h
[root@vm1 ~]# kubectl logs calico-kube-controllers-56cdb7c587-ndxrk -n kube-system
[root@vm1 ~]# kubectl get events -n kube-system
LAST SEEN   TYPE      REASON             OBJECT                                         MESSAGE
3m20s       Warning   FailedScheduling   pod/calico-kube-controllers-56cdb7c587-ndxrk   0/1 nodes are available: 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }. preemption: 0/1 nodes are available: 1 Preemption is not helpful for scheduling.

Control plane node isolation

보안상의 이유로 control plane 노드에서는 schedule Pods 예약이 되지 않아 calico-kube-controllers 가 실행되지 않고 있었다. 단일 머신 k8s 클러스터에서, 혹은 control plane에서 실행하고자 할 경우 다음을 실행하여 taint 를 변경하여 준다.

kubectl taint nodes --all node-role.kubernetes.io/control-plane- node-role.kubernetes.io/master-

3.9. Dashboard UI

대시보드 UI는 기본으로 배포되지 않는다. 배포하려면 다음 커맨드를 실행한다.

kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.4.0/aio/deploy/recommended.yaml

4. Haproxy 서버 구성

앞서 kubernetes 설치 과정에서 master 노드의 HA 구성에 사용되는 HAProxy 서버 구성에 대해 설명한다.
클러스터 구성과 동일한 CentOS 7 서버를 최소 설치 사양으로 구성한다.
이후 selinux와 firewalld 를 비활성화 하고, haproxy 서비스를 설치한다.

systemctl stop firewalld
systemctl disable firewalld
sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
setenforce 0
yum install -y haproxy

고가용성 쿠버네티스 클러스터를 구성하기 위해서 다음과 같이 kubernetes master node에 대한 HA 구성을 한다.

cat <<EOF | tee /etc/haproxy/haproxy.cfg

global
    log         127.0.0.1 local2

    chroot      /var/lib/haproxy
    pidfile     /var/run/haproxy.pid
    maxconn     4000
    user        haproxy
    group       haproxy
    daemon
    stats socket /var/lib/haproxy/stats

#---------------------------------------------------------------------
# common defaults that all the 'listen' and 'backend' sections will
# use if not designated in their block
#---------------------------------------------------------------------
defaults
    log global
    option  httplog
    option  dontlognull
    timeout connect 5000
    timeout client 50000
    timeout server 50000

#---------------------------------------------------------------------
# main frontend which proxys to the backends
#---------------------------------------------------------------------
frontend  main *:6443
    option tcplog
    default_backend kube-apiserver

#---------------------------------------------------------------------
# static backend for serving up images, stylesheets and such
#---------------------------------------------------------------------
backend static
    balance     roundrobin
    server      static 127.0.0.1:4331 check

#---------------------------------------------------------------------
# round robin balancing between the various backends
#---------------------------------------------------------------------
backend kube-apiserver
    mode    tcp
    option  tcplog
    option  tcp-check
    balance roundrobin
    default-server inter 10s downinter 5s rise 2 fall 2 slowstart 60s maxconn 250 maxqueue 256 weight 100
    server  kube-master1 192.168.0.11:6443 check
    server  kube-master2 192.168.0.12:6443 check
    server  kube-master3 192.168.0.13:6443 check

#---------------------------------------------------------------------
# stats frontend
#---------------------------------------------------------------------
listen stats
    bind *:10000
    mode http
    timeout client 5000
    timeout connect 4000
    timeout server 30000
    #stats enable
    stats uri /
    #stats refresh 10s
    stats realm Kube-api-server haproxy statistics
    stats auth admin:admin
    stats admin if TRUE
EOF

Haproxy 서비스를 기동하고 시스템 시작 서비스로 등록한다.

systemctl enable haproxy --now  
systemctl start haproxy

troubleshooting

만일 haproxy가 "HAProxy cannot bind socket" 에러를 발생시키면,
SELinux 보안 정책에 막혀서 그럴 가능성이 매우 높으므로, haproxy가 생성한 포트를 모두 허용하도록 변경합니다.

# selinux를 비활성화 하거나,
# 다음과 같이 haproxy 설정을 추가해 줍니다.
setsebool -P haproxy_connect_any=1

https://stackoverflow.com/questions/34793885/haproxy-cannot-bind-socket-0-0-0-08888
https://www.digitalocean.com/community/tutorials/haproxy-network-error-cannot-bind-socket

5. Docker private registry 구축

도커 공식 저장소가 아닌 개인용 private registry를 구축해서 도커 이미지를 관리할 수 있는데, 이를 사용하여 쿠버네티스 클러스터 구성 및 배포 관리에 사용한다.
다음은 위 도커 및 쿠버네티스 설치 과정에서 사용된 docker private registry 구성 과정이다.

Registry를 운영할 서버(VM) 을 준비하여 docker 환경을 설치한다.
이후 docker hub 공식 저장소의 registry 이미지를 다운받아 private registry를 구성한다.

만일 기 운영중인 도커 환경이 있다면, 또는 kubernetes cluster 상에 구성해도 무방하다.

docker-compose.yml 작성 예시

version: "2.1"
services:
  docker-registry:
    image: registry:latest
    container_name: registry
    environment:
      - TZ=Asia/Seoul
    ports:
      - 5000:5000
    volumes:
      - ./registry:/var/lib/registry/docker/registry

신규 VM 에 docker 설치

yum install -y yum-utils; \
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo; \
yum install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin; \
adduser docker -g docker; \
usermod -G docker docker; \
systemctl start docker; \
systemctl enable docker; \
systemctl stop firewalld; \
systemctl disable firewalld; \
sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config; \
setenforce 0; \

private registry에 접근할 호스트에서 insecure-registries 설정하기

docker 호스트에서 private registry 에 접근하려다 보면 security 문제로 접근이 안된다.
이때 다음과 같이 dockerd 설정에 insecure-registries 를 추가해준다.

# cat /etc/docker/daemon.json
{
    "insecure-registries": ["192.168.0.19:5000"]
}

사설 인증서 발급 및 HTTPS 설정

이런 저런 에러를 만나다 보면 차라리 SSL 설정을 해두는 편이 오히려 더 편할 것 같다고 느껴질 수 있다.
그래서 사설 인증서를 발급하여 registry에 SSL 적용을 하는 방법을 설명한다.

hostname 설정

registry에 접근할 호스트의 /etc/hosts 파일에 다음과 같이 registry 주소를 설정해 준다.

192.168.0.19 docker-registry.private.net

사설 인증서 발급

$ mkdir -p /work/registry/certs
$ cd /work/registry/certs

# 다음 설정을 하지 않으면 "x509: cannot validate certificate for because it doesn't contain any IP SANs" 에러를 발생합니다.
$ echo subjectAltName = IP:192.168.0.19,IP:127.0.0.1 > extfile.cnf

# 인증서 발급
$ openssl genrsa -out registry.key 2048
$ openssl req -new -key registry.key -out registry.csr
$ openssl x509 -req -days 365 -in registry.csr -signkey registry.key -out registry.crt -extfile extfile.cnf


이 과정에서 다음의 값만 입력 후 모두 빈 값을 입력합니다.
Common Name (eg, your name or your server's hostname) []:docker-registry.private.net

사설 인증서 CA 복사 (registry 호스트 및 클라이언트 )

cp registry.crt /etc/pki/ca-trust/source/anchors/registry.crt
update-ca-trust

#### 레지스트리에 접근하려는 호스트에 인증서 등록하기
mkdir -p /etc/docker/certs.d/docker-registry.private.net:5000
cp /etc/pki/ca-trust/source/anchors/registry.crt /etc/docker/certs.d/docker-registry.private.net\:5000/
systemctl restart containerd
systemctl restart docker

docker registry compose 파일 수정

version: "3.1"

services:
  docker-registry:
    image: registry:latest
    container_name: registry
    environment:
      - TZ=Asia/Seoul
      - REGISTRY_HTTP_TLS_CERTIFICATE=/certs/registry.crt
      - REGISTRY_HTTP_TLS_KEY=/certs/registry.key
      - REGISTRY_HTTP_ADDR=0.0.0.0:5000
    ports:
      - 5000:5000
    restart: always
    volumes:
      - ./registry:/var/lib/registry
      - ./certs:/certs

6. 환경변수

6.1. bash alias

alias dps='docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Size}}\t{{.RunningFor}}\t{{.Ports}}"'
alias dstat='docker stats --format "table {{.Name}}\t{{.PIDs}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}"'

echo alias dps=\'docker ps -a --format \"table {{.Names}}\\t{{.Status}}\\t{{.Size}}\\t{{.RunningFor}}\\t{{.Ports}}\"\' >> ~/.bash_profile
echo alias dstat=\'docker stats --format \"table {{.Name}}\\t{{.PIDs}}\\t{{.CPUPerc}}\\t{{.MemUsage}}\\t{{.MemPerc}}\\t{{.NetIO}}\\t{{.BlockIO}}\"\' >> ~/.bash_profile

6.2. History 출력 날짜 포멧 및 리스트 갯수

# /etc/profile 에 추가
HISTTIMEFORMAT="[%F %T] "
HISTSIZE=10000
HISTFILESIZE=20000

export HISTTIMEFORMAT HISTSIZE HISTFILESIZE

6.3. bash-completion

yum install -y bash-completion
kubectl completion bash >> ~/.bashrc

curl -L https://raw.githubusercontent.com/docker/cli/v$(docker version --format '{{.Server.Version}}' | sed 's/-.*//')/contrib/completion/bash/docker >> ~/.bashrc

Reference