Kubernetes

PKOS-kOps-2Week

ㅠㅠ 울고 시작하려한다. 스터디에 집중을 하려고 한다.
가시다님 그동안 숙제 너무 조금해서 죄송했어요...ㅠㅠ엉엉흑흑

일단 사과를 드리고 시작하며, 이제 살짝 각잡고 kOps 부터 설명하겠다.

kOps는 Kubernetes Operations의 약자로, Kubernetes 클러스터를 AWS (Amazon Web Services)에서 손쉽게 설치, 업그레이드 및 관리할 수 있도록 해주는 오픈 소스 도구이다. Kops를 사용하면 CLI(Command Line Interface)를 통해 클러스터를 구성할 수 있으며, YAML 파일을 사용하여 쉽게 클러스터를 정의할 수 있다

Kops는 여러 가지 기능을 제공한다

  1. 클러스터 구성: Kops를 사용하여 Kubernetes 클러스터를 쉽게 구성할 수 있다. YAML 파일을 사용하여 클러스터 구성을 정의하고, AWS 리소스를 프로비저닝하고 구성을 배포한다.
  2. 노드 그룹: Kops는 노드 그룹을 사용하여 클러스터 내에서 다양한 유형의 노드를 정의할 수 있다. 예를 들어, CPU 또는 메모리 요구 사항이 높은 애플리케이션을 실행하는 데 필요한 노드 그룹을 만들 수 있다.
  3. 클러스터 업그레이드: Kops를 사용하여 클러스터를 업그레이드하면, 기존 클러스터의 구성 및 애플리케이션 상태가 유지된다. 이는 클러스터 업그레이드가 더욱 안정적이며 안전하게 진행될 수 있도록 도와준다.
  4. 롤링 업데이트: Kops는 클러스터의 노드 그룹을 업데이트할 때 롤링 업데이트를 수행할 수 있다. 이를 통해 클러스터에 대한 서비스 중단 없이 노드 그룹의 업데이트를 진행할 수 있다.

Kops는 쉽게 시작할 수 있는 Kubernetes 설치 및 관리 도구 중 하나이다, AWS에서 Kubernetes 클러스터를 운영하는 데 매우 유용하다.

PKOS에서는 24단계 실습으로 정복하는 쿠버네티스 로 실습을 진행하면서 kOps를 사용한다.

나는 EKS 를 근래에 주로 다루고 있다.

kOps 와 EKS를 간략하게 비교해봤다.

기능 / 속성kOpsEKS
관리 형태오픈 소스 도구완전 관리형 서비스
클라우드 플랫폼AWS, GCP 등 다양한 플랫폼AWS 전용
클러스터 구축사용자 정의 구성 가능표준화된 구성 사용
클러스터 업그레이드사용자가 직접 관리AWS가 제공하는 관리형 업그레이드
클러스터 유효성 검사kOps 도구를 사용하여 제공AWS 콘솔 및 API를 통해 제공
비용인프라 리소스 비용만 발생인프라 리소스 및 EKS 서비스 비용
운영의 편의성사용자가 더 많은 관리를 수행함AWS가 더 많은 관리를 처리함

역시 관리형이 편하다.

이번주에는 K8S의 네트워킹에 대해서 공부를 했는데 다른점이 많은 kOps 와 EKS지만 kOps 를 사용하면 EKS를 배우기 편한 부분이 있다. 이유는 에드온이나, CNI를 같은것을 사용할수 있다.

awsLoadBalancerController / CNI로는 amazonvpc 를 사용한다.

내가 이야기할 것은 CNI다.

amazonvpc CNI 같은 경우에는 한가지 특징이 있는데 AWS ENI를 컨테이너에 연결하여 일반적으로 우리가 사용하는 kube-proxy의 동작이 현저하게 줄어들게 된다.

kube-proxy의 동작이 줄어드는 아키텍처는 노드에서의 네트워크 처리횟수가 줄어들어 CPU의 사용량이 줄어든다. 기본적으로 리눅스 네트워크 스택은 자원의 사용량이 적지만 네트워크의 미학을 가진 K8S는 적극적으로 리눅스네트워크 스택을 사용한다. 그러므로 노드의 부하는 커진다. 이런 문제를 amazonvpc CNI는 회피할수 있는 방법을 제시한것이다.

POD가 클러스터를 통하지 않고 통신하는 방식은 Network Hop을 줄이기 때문에 일반적인 오버레이 네트워크의 Network Hop보다 현저하게 줄어들고 빠른 방식이 가능한것이다.

이때문에 kube-proxy를 쓰는 nodeport 같은 형태의 Sevice는 EKS에 어울리지 않는다.

반드시 awsLoadBalancerController 를 사용할떈 target option을 IP로 사용하길 추천한다.

pod graceful shutdown

    spec:
      containers:
        - name: nginx
          image: nginx:latest
          ports:
            - containerPort: 80
          resources:
            limits:
              cpu: 500m
            requests:
              cpu: 200m
          lifecycle:
            preStop:
              exec:
                command: ["/bin/sleep", "30"]

.

.

hpa 발생시 pod의 갑작스런 종료로 pod에 연결된 사용자가 502를 받게된다.

모든 리퀘스트를 처리 후에 종료되도록 30초간의 유예를 준다 설정 변경 lifecycle preStop 를 이용하여 우아한 종료를!

PKOS-kOps-1Week

이번에 스터디에 참가하게 되었다.

가시다님의 PKOS!

스터디할시에 사용하는 책은 24단계 실습으로 정복하는 쿠버네티스 이다.

kOps를 프로비저닝하는데 오타가 발생해서 심심해서 스크립트를 만들었다.
그덕에 한번 다시 만들었다.

#!/bin/bash

echo "클러스터명-도메인을 입력해주세요 : "
read KOPS_CLUSTER_NAME
echo "버킷명을 입력해 주세요 s3:// 는 입력하지 않아도 됩니다. : "
read  KOPS_STATE_STORE
# Access Key를 입력 받음
read -p "엑세스키를 입력해주세요 : " ACCESS_KEY

# Secret Access Key를 입력 받음
read -p "시크릿키를 입력해주세요 : " SECRET_KEY

# AWS 계정 구성
aws configure set aws_access_key_id $ACCESS_KEY
aws configure set aws_secret_access_key $SECRET_KEY
echo 'export AWS_PAGER=""' >>~/.bashrc
echo "export REGION=ap-northeast-2" >>~/.bashrc
echo "export KOPS_CLUSTER_NAME=$KOPS_CLUSTER_NAME" >>~/.bashrc
echo "export KOPS_STATE_STORE=s3://$KOPS_STATE_STORE" >>~/.bashrc

kops create cluster --zones="$REGION"a,"$REGION"c --networking amazonvpc --cloud aws \
--master-size t3.medium --node-size t3.medium --node-count=2 --network-cidr 172.30.0.0/16 \
--ssh-public-key ~/.ssh/id_rsa.pub --name=$KOPS_CLUSTER_NAME --kubernetes-version "1.24.10" --dry-run -o yaml > mykops.yaml


kops create cluster --zones="$REGION"a,"$REGION"c --networking amazonvpc --cloud aws \
--master-size t3.medium --node-size t3.medium --node-count=2 --network-cidr 172.30.0.0/16 \
--ssh-public-key ~/.ssh/id_rsa.pub --name=$KOPS_CLUSTER_NAME --kubernetes-version "1.24.10" -y

source <(kubectl completion bash)
echo 'source <(kubectl completion bash)' >> ~/.bashrc
echo 'alias k=kubectl' >> ~/.bashrc
echo 'complete -F __start_kubectl k' >> ~/.bashrc

read 명령어를 이용하여 스크립트에 변수를 부여하고 입력받은 변수를 이용하여 aws configure 를 설정하고, kops 명령어로 k8s 클러스터를 프로비저닝한다.

이거다음에는 사실 initscript 에 내가원하는 값을 넣는게 제일 편하나 그건..좀 공개하기 애매하니 스크립트라도 공개한다.

Kubernetes-Mysql-Operator

Mysql Operator 스터디를 진행중에 느꼈다.
Mysql Operator는 현재 나의 판단보다 Operator의 로직이 훨신 빠르고 정확하게 동작할거라는 생각이 들었다.

다른말로는 믿고 써도 될수준에 가깝다 느껴졌다. 물론 RDS 못잃어

https://dev.mysql.com/doc/mysql-operator/en/mysql-operator-introduction.html

그래서 나는 실습은 그냥..helm으로 다들 하는것 같아서 Operator가 생성하는 Mysql Cluster 의 아키텍처를 파해쳐 볼까한다.

DNS - SRV Record

https://www.haproxy.com/documentation/hapee/latest/management/service-discovery/dns-service-discovery/discovery-with-srv-records/

SRV 레코드는 자주 사용 되는 레코드는 아니지만, GSLB혹은 가중치를 이용한 라우팅, 페일오버 등에 사용된다.

수진님의 포스팅 링크

https://dev.mysql.com/doc/refman/8.0/en/connecting-using-dns-srv.html

정리하자면 Mysql Routor 에서 클러스터의 노드별로 srv레코드를 부여하고,

https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-dns-srv.html

mysql 8.0.19버전에서 Connector 에서 또한 srv 레코드를 이용할수 있게되어서 Mysql 클러스터는 획기적으로 발전한것이다. 이전에는 mysql 의 HA를 구성하려면 FaceMaker 부터 시작해서 손이 갈부분이 만만치 않게 많았다.

오라클로 인수된 Mysql의 약진이 정말 기대이상이다.

X Protocol

https://dev.mysql.com/doc/internals/en/x-protocol.html

X Protocol 은 5.7.12 버전부터 플러그인으로 사용할수 있었다.

https://dev.mysql.com/doc/internals/en/x-protocol.html

X protocol 은 Life Cycle, Notifocation 등의 기능을 담당하는데, 이 프로토콜로 서버의 정상유무를 라우터나 오퍼레이터와 통신하여 처리한다.

https://dev.mysql.com/doc/internals/en/x-protocol-lifecycle-lifecycle.html

Backup

Backup는 다이렉트로 S3백업이 가능하다는 점이 인상적인데, 정말 바라던 기능이라 충격적일 정도이다.

https://github.com/bitpoke/mysql-operator/blob/master/docs/backups.md

다른 Operator 와 비교해보고 싶다면 아래 블로그를 참고하길 바란다.

https://portworx.com/blog/choosing-a-kubernetes-operator-for-mysql/

마치며

SRV레코드를 이용하여 Endpoint를 특정하고, X protocol을 이용하여 헬스체크를 하고, 문제가 생기면, 오퍼레이터가 파드를 다시 생성하는 등의 과정이 이루어 진다. 말로는 정말 간단하다. 하지만 이 아키텍처가 쿠버네티스의 탄력성과 신속성과 합쳐짐으로 강력한 시너지를 발생하는것으로 보인다.

사용자의 고민이 많이 없이 사용할수있는 레벨의 솔루션으로 진화한듯싶다.

그간 많은 엔지니어 들이 사랑하던 Mysql 의 진화가 반갑기만하면서..오라클의 품에 있는 My가 두렵다.

그래서 MariaDB Operator를 찾아보았다.

https://mariadb.com/kb/en/kubernetes-operators-for-mariadb/

그런데 MariaDB Operator는 이전에 Galera Operator라 불리던 작품인듯한다.

다음에 한번 테스트를 해봐야 겠다....근데 사용자가 정말 없는듯하다.

오퍼레이터 스터디로 인하여 컨테이너 디비에 대해서 새로운 관점이 생기는 것을 느낀다.

DOIK-Study

가시다님과 함께하는 스터디는 항상 즐겁다. 이번 스터디엔 라이브로 못해서..일단 바닐라쿠버 배포하고 시작했다.

Headless 서비스는 ClusterIP가 None으로 설정하며 kubeproxy를 통하지않고 pod의 endpoint가 svc에 연결된다.

나는 먼저 NFS 서버를 Headless 로 배포하기로 했다.

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: nfs-server-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
      
---

kind: Service
apiVersion: v1
metadata:
  name: nfs-service
spec:
  type: ClusterIP
  clusterIP: None
  selector:
    role: nfs
  ports:
    # Open the ports required by the NFS server
    # Port 2049 for TCP
    - name: tcp-2049
      port: 2049
      protocol: TCP

    # Port 111 for UDP
    - name: udp-111
      port: 111
      protocol: UDP
      
    # Port 20048 for TCP
    - name: tcp-20048
      port: 20048
      protocol: TCP
---

apiVersion: v1
kind: ReplicationController
metadata:
  name: nfs-server
spec:
  replicas: 1
  selector:
    role: nfs-server
  template:
    metadata:
      labels:
        role: nfs-server
    spec:
      containers:
      - name: nfs-server
        image: gcr.io/google_containers/volume-nfs:0.8
        ports:
          - name: nfs
            containerPort: 2049
          - name: mountd
            containerPort: 20048
          - name: rpcbind
            containerPort: 111
        securityContext:
          privileged: true
        volumeMounts:
          - mountPath: /exports
            name: nfs-export
      volumes:
        - name: nfs-export-fast
          persistentVolumeClaim:
            claimName: nfs-server-pvc-fast
            

🐤

yaml을 deploy 하면 다음과같다.

이제 프로비저너 셋팅이 좀 필요하다. 가시다님께서는 친절하게 프로비저너 셋팅도 다해주셨지만 나는 내가만든 NFS 서버를 사용할거기 때문에 프로비저너를 다시 배포할거다.

#지우고
helm delete -n kube-system nfs-provisioner
#다시 설치하고
helm install nfs-provisioner -n kube-system nfs-subdir-external-provisioner/nfs-subdir-external-provisioner --set nfs.server=nfs-service.default.svc.cluster.local --set nfs.path=/exports
NAME: nfs-provisioner
LAST DEPLOYED: Thu May 26 16:10:31 2022
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None

🐤

응 잘됬다.

(🐤 |DOIK-Lab:default) root@k8s-m:~# k get svc
NAME          TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)            AGE
kubernetes    ClusterIP   10.200.1.1   <none>        443/TCP            4h30m
nfs-service   ClusterIP   None         <none>        2049/TCP,111/UDP   6m29s

(🐤 |DOIK-Lab:default) root@k8s-m:~# k get ep
NAME          ENDPOINTS                            AGE
kubernetes    192.168.10.10:6443                   4h30m
nfs-service   172.16.158.2:2049,172.16.158.2:111   6m48s

(🐤 |DOIK-Lab:default) root@k8s-m:~# k get pod -o wide
NAME             READY   STATUS    RESTARTS   AGE    IP             NODE     NOMINATED NODE   READINESS GATES
nfs-server-pod   1/1     Running   0          7m1s   172.16.158.2   k8s-w1   <none>           <none>

🐤

정상적으로 NFS서버가 잘 배포된것을 확인할수 있다.

apiVersion: v1
kind: PersistentVolume
metadata:
  name: mysql-nfs-pv
  labels:
    type: mysql-nfs-pv
spec:
  storageClassName: nfs-client
  capacity:
    storage: 4Gi
  accessModes:
  - ReadWriteOnce          # ReadWriteOnce RWO (1:1 마운트, 읽기 쓰기)
  nfs:
    server: 172.16.184.9 # NFS-Server 의 IP
    path: /1       # NFS 저장소
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: wp-nfs-pv
  labels:
    type: wp-nfs-pv
spec:
  storageClassName: nfs-client
  capacity:
    storage: 4Gi
  accessModes:
  - ReadWriteOnce
  nfs:
    server: 172.16.184.9 # NFS-Server 의 IP
    path: /2      # NFS 저장소

🐤

nfs-service.svc.cluster.local domain을 이용하려 하였으나, PV 에서 domain으로 설정시 nfs-provisioner 정상적으로 마운트 되지 않았다.

headless NFS를 하려고 한것이나, 실패하였다.
지원하지 않는다.(결론)

다음과 같은 증상이었다. IP로 프로비저너 설치후엔 잘되었다.

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  labels:
    app: wordpress
  name: mysql-pv-claim
spec:
  storageClassName: nfs-client
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 4Gi
  selector:
    matchLabels:
      type: "mysql-nfs-pv"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  labels:
    app: wordpress
  name: wp-pv-claim
spec:
  storageClassName: nfs-client
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 4Gi
  selector:
    matchLabels:
      type: "wp-nfs-pv"

🐤

selector를 이용하여 PV를 사용하도록 설정해 주었다

Every 2.0s: kubectl get svc,pods,pv,pvc -o wide                                                                                                                                                                    k8s-m: Thu May 26 18:10:41 2022

NAME                      TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)                      AGE     SELECTOR
service/kubernetes        ClusterIP   10.200.1.1    <none>        443/TCP                      7h56m   <none>
service/nfs-service       ClusterIP   None          <none>        2049/TCP,111/UDP,20048/TCP   71m     role=nfs
service/wordpress         NodePort    10.200.1.33   <none>        80:30387/TCP                 3m25s   app=wordpress,tier=frontend
service/wordpress-mysql   ClusterIP   None          <none>        3306/TCP                     3m25s   app=wordpress,tier=mysql

NAME                                   READY   STATUS    RESTARTS   AGE     IP              NODE     NOMINATED NODE   READINESS GATES
pod/nfs-server-rxvf7                   1/1     Running   0          71m     172.16.184.9    k8s-w2   <none>           <none>
pod/wordpress-859f989bbb-msppd         1/1     Running   0          3m25s   172.16.158.21   k8s-w1   <none>           <none>
pod/wordpress-mysql-66fb7cfb68-z9vj5   1/1     Running   0          3m25s   172.16.158.20   k8s-w1   <none>           <none>

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                         STORAGECLASS   REASON   AGE     VOLUMEMODE
persistentvolume/mysql-nfs-pv                               4Gi        RWO            Retain           Bound    default/mysql-pv-claim        nfs-client              3m32s   Filesystem
persistentvolume/pvc-adc24c97-ca67-4700-b3c5-2fc51c4cce01   10Gi       RWO            Delete           Bound    default/nfs-server-pvc-fast   local-path              71m     Filesystem
persistentvolume/wp-nfs-pv                                  4Gi        RWO            Retain           Bound    default/wp-pv-claim           nfs-client              3m32s   Filesystem

NAME                                        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE     VOLUMEMODE
persistentvolumeclaim/mysql-pv-claim        Bound    mysql-nfs-pv                               4Gi        RWO            nfs-client     3m25s   Filesystem
persistentvolumeclaim/nfs-server-pvc-fast   Bound    pvc-adc24c97-ca67-4700-b3c5-2fc51c4cce01   10Gi       RWO            local-path     71m     Filesystem
persistentvolumeclaim/wp-pv-claim           Bound    wp-nfs-pv                                  4Gi        RWO            nfs-client     3m25s   Filesystem

🐤

서비스가 잘 작동하는것을 확인하였다.

(🐤 |DOIK-Lab:default) root@k8s-m:~/yaml/yaml# k exec nfs-server-rxvf7 -it -- /bin/bash
[root@nfs-server-rxvf7 /]# cd /exports/
[root@nfs-server-rxvf7 exports]# ll
total 32
drwxr-xr-x 5 systemd-bus-proxy root 4096 May 26 09:07 1
drwxr-xr-x 5                33   33 4096 May 26 09:07 2
-rw-r--r-- 1 root              root   16 May 26 07:59 index.html
[root@nfs-server-rxvf7 exports]# cd 1
[root@nfs-server-rxvf7 1]# ll
total 110608
-rw-rw---- 1 systemd-bus-proxy input       56 May 26 09:07 auto.cnf
-rw-rw---- 1 systemd-bus-proxy input 50331648 May 26 09:07 ib_logfile0
-rw-rw---- 1 systemd-bus-proxy input 50331648 May 26 09:07 ib_logfile1
-rw-rw---- 1 systemd-bus-proxy input 12582912 May 26 09:07 ibdata1
drwx------ 2 systemd-bus-proxy input     4096 May 26 09:07 mysql
drwx------ 2 systemd-bus-proxy input     4096 May 26 09:07 performance_schema
drwx------ 2 systemd-bus-proxy input     4096 May 26 09:07 wordpress
[root@nfs-server-rxvf7 1]# cd ..
[root@nfs-server-rxvf7 exports]# cd 2
[root@nfs-server-rxvf7 2]# ll
total 192
-rw-r--r--  1 33 33   418 Sep 25  2013 index.php
-rw-r--r--  1 33 33 19935 Jan  2  2017 license.txt
-rw-r--r--  1 33 33  7413 Dec 12  2016 readme.html
-rw-r--r--  1 33 33  5447 Sep 27  2016 wp-activate.php
drwxr-xr-x  9 33 33  4096 Oct 31  2017 wp-admin
-rw-r--r--  1 33 33   364 Dec 19  2015 wp-blog-header.php
-rw-r--r--  1 33 33  1627 Aug 29  2016 wp-comments-post.php
-rw-r--r--  1 33 33  2764 May 26 09:07 wp-config-sample.php
-rw-r--r--  1 33 33  3154 May 26 09:07 wp-config.php
drwxr-xr-x  4 33 33  4096 Oct 31  2017 wp-content
-rw-r--r--  1 33 33  3286 May 24  2015 wp-cron.php
drwxr-xr-x 18 33 33 12288 Oct 31  2017 wp-includes
-rw-r--r--  1 33 33  2422 Nov 21  2016 wp-links-opml.php
-rw-r--r--  1 33 33  3301 Oct 25  2016 wp-load.php
-rw-r--r--  1 33 33 34327 May 12  2017 wp-login.php
-rw-r--r--  1 33 33  8048 Jan 11  2017 wp-mail.php
-rw-r--r--  1 33 33 16200 Apr  6  2017 wp-settings.php
-rw-r--r--  1 33 33 29924 Jan 24  2017 wp-signup.php
-rw-r--r--  1 33 33  4513 Oct 14  2016 wp-trackback.php
-rw-r--r--  1 33 33  3065 Aug 31  2016 xmlrpc.php

🐤

목적이었던 NFS도 정상적으로 작동한다.

(🐤 |DOIK-Lab:default) root@k8s-m:~/yaml# k scale deployment wordpress --replicas=3

(🐤 |DOIK-Lab:default) root@k8s-m:~/yaml# k get pod
NAME                               READY   STATUS    RESTARTS   AGE
nfs-server-rxvf7                   1/1     Running   0          77m
wordpress-859f989bbb-8r5zh         1/1     Running   0          47s
wordpress-859f989bbb-msppd         1/1     Running   0          8m38s
wordpress-859f989bbb-xhbs9         1/1     Running   0          47s
wordpress-mysql-66fb7cfb68-z9vj5   1/1     Running   0          8m38s

(🐤 |DOIK-Lab:default) root@k8s-m:~/yaml# k get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                         STORAGECLASS   REASON   AGE
mysql-nfs-pv                               4Gi        RWO            Retain           Bound    default/mysql-pv-claim        nfs-client              9m6s
pvc-adc24c97-ca67-4700-b3c5-2fc51c4cce01   10Gi       RWO            Delete           Bound    default/nfs-server-pvc-fast   local-path              77m
wp-nfs-pv                                  4Gi        RWO            Retain           Bound    default/wp-pv-claim           nfs-client              9m6s
(🐤 |DOIK-Lab:default) root@k8s-m:~/yaml# k get pvc
NAME                  STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
mysql-pv-claim        Bound    mysql-nfs-pv                               4Gi        RWO            nfs-client     9m2s
nfs-server-pvc-fast   Bound    pvc-adc24c97-ca67-4700-b3c5-2fc51c4cce01   10Gi       RWO            local-path     77m
wp-pv-claim           Bound    wp-nfs-pv                                  4Gi        RWO            nfs-client     9m2s

🐤

볼륨도 잘공유하여 프로비저닝 된것을 확인할수 있다. ㅜㅜ