K8s 환경에서 NFS 노드 볼륨 구성

Dierslair·2022년 6월 1일
2

kubernetes

목록 보기
3/5

프로젝트 서버 설정 작업을 맡게 되었습니다. 요청 사항 중에서 'n개의 서버 병렬 운용이 가능할 것' 이라는 게 있었는데 특정 상황(행사 등)에서 요청이 폭주하여 서버가 다운된 경험이 있는 듯 해 보였습니다.

서버 인스턴스를 여럿 생성하고, 각 서버의 역할을 정하여 수동으로 운영하는 경우도 생각 해 보았으나 나중에 가면 운영상 귀찮아질 것 같아(수동 배포 헬) 쿠버네티스를 사용하기로 했습니다.

노드 구조

애플리케이션을 병렬 운영하는 경우 당장 신경써야 할 부분이
1) 세션 클러스터링
2) 캐시 저장소
3) 업로드 파일 저장소

정도가 있습니다. 1, 2야 스프링에서 제공해 주는 세션 모듈과 캐시 모듈을 사용하면 되나, 업로드 파일 관리는 마이크로서비스로 분리해야 하나 고민했지만 서비스 성격이 모놀리틱 구조였으므로 마이크로서비스로의 확장은 고려하지 않기로 했습니다.

PersistentVolume

컨테이너를 재시작하게 되면 컨테이너 내부의 데이터는 모두 사라지기 때문에 호스트 서버 의 볼륨과 컨테이너 내부의 볼륨을 마운트하여 유지하기 위해 사용하는 것이 PersistentVolume(이하 PV) 입니다.

호스트 서버의 공간 일부를 PV 볼륨으로 할당하여 사용하는 것이 가장 간단하나 이 경우 하나의 노드만 PV 를 사용할 수 있기 때문에 애플리케이션의 수평 확장은 불가능합니다.

그렇다고 저장소 서비스를 사용하기에는 애매한 상황이라 NFS (Network File System) 를 사용하여 한 번에 여러 노드의 접근을 가능하게 하는 nfs 타입의 PV 를 사용하기로 했습니다.

NFS 서버 구성

우분투 환경에서 NFS 서버를 구성하기 위해서는 다음 패키지가 필요합니다.

$ sudo apt-get update
$ sudo apt-get install -y nfs-common nfs-kernel-server rpcbind portmap

NFS 서버에서 공유할 디렉터리를 생성합니다.

$ cd /mnt
$ sudo mkdir shared
$ sudo chmod 777 shared

NFS 서비스 설정파일을 수정합니다. 공유할 디렉터리 경로와 현재 사용하고 있는 서버 인스턴스의 서브넷 범위를 지정합니다.

$ sudo echo '/mnt/shared 172.26.0.0/16(rw,sync,no_subtree_check)' >> /etc/exports

설정을 적용하고 서비스를 재시작합니다.

$ sudo exportfs -a
$ sudo systemctl restart nfs-kernel-server

NFS 서버에 접근하는 노드에는 nfs-common 패키지만 설치하면 됩니다.

PersistentVolumePersistentVolumeClaim 생성

NFS 서버의 Private IP 를 사용하여 PV, PVC 를 생성합니다.

# nfs-pv.yml
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-storage
  labels:
    type: nfs
spec:
  capacity:
    storage: 10Gi
  accessModes: ["ReadWriteMany"]
  nfs:
    server: [NFS_SERVER_IP]
    path: /mnt/shared # NFS server's shared path

# nfs-pvc.yml
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-storage-claim
spec:
  storageClassName: "" # 빈 문자열을 줍니다(중요)
  accessModes: ["ReadWriteMany"]
  resources:
    requests:
      storage: 4Gi
  selector:
    matchExpressions:
      - key: type
        operator: In
        values:
          - nfs
$ kubectl apply -f nfs-pv.yml
$ kubectl apply -f nfs-pvc.yml
$ kubectl get pv,pvc
===
NAME                           CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                       STORAGECLASS   REASON   AGE
persistentvolume/nfs-storage   10Gi       RWX            Retain           Bound    default/nfs-storage-claim                           40m

NAME                                      STATUS   VOLUME        CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/nfs-storage-claim   Bound    nfs-storage   10Gi       RWX                           40m

애플리케이션에서 볼륨 사용해 보기

NFS 파일 공유가 정상적으로 처리되는지 확인하기 위해 간단한 rest application 을 작성합니다.

@Controller
class GreetingController {
    private val directory: Path = Paths.get("/data")

    @GetMapping
    @ResponseBody
    fun index(): String {
        val target = this.directory.resolve("file.txt")

        if (Files.exists(target).not()) {
            Files.newOutputStream(target)
                .use { output ->
                    val current = System.currentTimeMillis()
                    val content = "current timestamp is >> $current"
                        .toByteArray(StandardCharsets.UTF_8)

                    output.write(content, 0, content.size)
                }
        }

        return Files.newInputStream(target)
            .use { StreamUtils.copyToString(it, StandardCharsets.UTF_8) }
    }
}

application.yml 파일을 생성합니다.

# application.yml
---
apiVersion: v1
kind: Service
metadata:
  name: application
  labels:
    role: application
spec:
  ports:
  - protocol: TCP
    port: 8080
    targetPort: 8080
  selector:
    role: application

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: application
spec:
  replicas: 2
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 0
      maxUnavailable: 1
  selector:
    matchLabels:
      role: application
  template:
    metadata:
      labels:
        role: application
    spec:
      imagePullSecrets:
        - name: johnsuhr4542
      containers:
      - name: my-application
        image: johnsuhr4542/my_app:latest
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
        volumeMounts:
          - name: application-storage
            mountPath: /data
      volumes:
        - name: application-storage
          persistentVolumeClaim:
            claimName: nfs-storage-claim
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: type
                operator: In
                values:
                - worker
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: role
                    values:
                      - application
                    operator: In
              topologyKey: kubernetes.io/hostname
              namespaces:
                - k8s-worker-1
                - k8s-worker-2
$ kubectl apply -f application.yml
$ kubectl get pods -o wide
===
NAME                           READY   STATUS        RESTARTS   AGE   IP           NODE           NOMINATED NODE   READINESS GATES
application-86b58b59f-p7hnv    1/1     Running       0          19s   10.244.2.7   k8s-worker-2   <none>           <none>
application-86b58b59f-qk7xk    1/1     Running       0          19s   10.244.1.6   k8s-worker-1   <none>           <none>

k8s-worker-1 에 생성된 파드 내부에서 애플리케이션 엔드포인트로 접근하면 파일이 생성됩니다.

$ kubectl exec -it pod/application-86b58b59f-qk7xk -- bash
$ apt-get update && apt-get install -y curl
$ curl http://localhost:8080 -i
===
HTTP/1.1 OK
...
current timestamp is >> 1654061688037

NFS 서버에서 공유 디렉터리에 파일이 생성되었는지 확인합니다.

ubuntu@k8s-storage:/mnt/shared$ ll
total 12
drwxrwxrwx 2 root   root    4096 Jun  1 05:34 ./
drwxr-xr-x 3 root   root    4096 Jun  1 04:52 ../
-rw-r--r-- 1 nobody nogroup   37 Jun  1 05:34 file.txt

k8s-worker-2 에 생성된 파드 내부에서 애플리케이션 엔드포인트로 접근하면 파일이 이미 있으니, 읽어서 내용을 반환합니다.

NFS 서버의 디렉터리에 생성된 파일의 권한과 그룹이 nobody , nogroup 으로 지정되기 때문에 보안에 취약하다는 단점이 있으나, Public 접근이 불가능한 Private 노드이므로 괜찮지 않을까(아닌가..) 싶습니다.

위 포스트는 블로그 포스트쿠버네티스 공식 문서 를 참고하여 작성되었습니다.

profile
Java/Kotlin Backend Developer

0개의 댓글