1장.Gitops란?

GitOps는 인프라와 애플리케이션 배포 구성을 Git으로 관리하는 운영 방식이다.

모든 환경 상태를 Git 리포지토리의 선언형 코드로 정의하고, 변경 사항은 Pull Request를 통해 자동으로 배포 된다. 이를 통해 일관성, 추적성, 자동화된 배포 및 롤백이 가능해진다

2장.실습준비

2.1. docker hub컨테이너 레지스트리 등록을 위한 액세스 토큰 생성

 

2.2 git저장소 등록(fork는 차주에 진행 실습을 위한 저장소 clone 진행)

 

git clone https://github.com/gitops-cookbook/chapters                                                                            Cloning into 'chapters'...
remote: Enumerating objects: 80, done.
remote: Counting objects: 100% (80/80), done.
remote: Compressing objects: 100% (55/55), done.
remote: Total 80 (delta 6), reused 74 (delta 4), pack-reused 0 (from 0)
Receiving objects: 100% (80/80), 64.80 KiB | 5.89 MiB/s, done.
Resolving deltas: 100% (6/6), done.

 

*이번 실습에선 사용하지 않지만 미리 포크 진행

 

2.3 로컬 쿠버네티스 클러스터 생성( WSL2(ubuntu) + Docker엔진 )

PS C:\WINDOWS\system32> dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart

배포 이미지 서비스 및 관리 도구
버전: 10.0.26100.1150

이미지 버전: 10.0.26100.3476

기능을 사용하도록 설정하는 중
[==========================100.0%==========================]
작업을 완료했습니다.
PS C:\WINDOWS\system32> dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart

배포 이미지 서비스 및 관리 도구
버전: 10.0.26100.1150

이미지 버전: 10.0.26100.3476

기능을 사용하도록 설정하는 중
[==========================100.0%==========================]
작업을 완료했습니다.
PS C:\WINDOWS\system32> wsl --set-default-version 2
WSL 2와의 주요 차이점에 대한 자세한 내용은 https://aka.ms/wsl2를 참조하세요

작업을 완료했습니다.
PS C:\WINDOWS\system32> wsl --update
업데이트 확인 중입니다.
Linux용 Windows 하위 시스템 최신 버전이 이미 설치되어 있습니다.
 PS C:\WINDOWS\system32> wsl --list --online
다음은 설치할 수 있는 유효한 배포 목록입니다.
'wsl.exe --install <Distro>'을 사용하여 설치합니다.

NAME                            FRIENDLY NAME
AlmaLinux-8                     AlmaLinux OS 8
AlmaLinux-9                     AlmaLinux OS 9
AlmaLinux-Kitten-10             AlmaLinux OS Kitten 10
AlmaLinux-10                    AlmaLinux OS 10
Debian                          Debian GNU/Linux
FedoraLinux-42                  Fedora Linux 42
SUSE-Linux-Enterprise-15-SP6    SUSE Linux Enterprise 15 SP6
SUSE-Linux-Enterprise-15-SP7    SUSE Linux Enterprise 15 SP7
Ubuntu                          Ubuntu
Ubuntu-24.04                    Ubuntu 24.04 LTS
archlinux                       Arch Linux
kali-linux                      Kali Linux Rolling
openSUSE-Tumbleweed             openSUSE Tumbleweed
openSUSE-Leap-16.0              openSUSE Leap 16.0
Ubuntu-20.04                    Ubuntu 20.04 LTS
Ubuntu-22.04                    Ubuntu 22.04 LTS
OracleLinux_7_9                 Oracle Linux 7.9
OracleLinux_8_10                Oracle Linux 8.10
OracleLinux_9_5                 Oracle Linux 9.5
openSUSE-Leap-15.6              openSUSE Leap 15.6
PS C:\WINDOWS\system32> wsl --install Ubuntu-24.04
다운로드 중: Ubuntu 24.04 LTS
설치 중: Ubuntu 24.04 LTS
배포가 설치되었습니다. 'wsl.exe -d Ubuntu-24.04'을(를) 통해 시작할 수 있습니다


 Static hostname: 4
       Icon name: computer-container
         Chassis: container ☐
      Machine ID: 3933a21596f342e3b7513d6cdffbdde5
         Boot ID: ef37164a0a9446ef8889b53d958c5778
  Virtualization: wsl
Operating System: Ubuntu 24.04.3 LTS
          Kernel: Linux 6.6.87.2-microsoft-standard-WSL2
    Architecture: x86-64
    
    ...........중략..................

 

1주차 실습을 위한 클러스터 구성

docker ps
CONTAINER ID   IMAGE                  COMMAND                  CREATED         STATUS         PORTS                       NAMES
28aa4b70c8f0   kindest/node:v1.34.0   "/usr/local/bin/entr…"   3 minutes ago   Up 3 minutes   127.0.0.1:34961->6443/tcp   kind-control-plane

kind create cluster --name myk8s --image kindest/node:v1.32.8 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 30000
    hostPort: 30000
  - containerPort: 30001
    hostPort: 30001
- role: worker
EOF
Creating cluster "myk8s" ...
 ✓ Ensuring node image (kindest/node:v1.32.8) 🖼
 ✓ Preparing nodes 📦 📦
 ✓ Writing configuration 📜
 ✓ Starting control-plane 🕹️
 ✓ Installing CNI 🔌
 ✓ Installing StorageClass 💾
 ✓ Joining worker nodes 🚜
Set kubectl context to "kind-myk8s"
You can now use your cluster with:

kubectl cluster-info --context kind-myk8s

Have a nice day! 👋

kind get nodes --name myk8s
myk8s-worker
myk8s-control-plane

kubens default
✔ Active namespace is "default"

docker-desktop 환경이기에 172.18.0.0/16을 기본값으로 가지고있다.

docker inspect kind | jq
[
  {
    "Name": "kind",
    "Id": "3ca55a71d5d74b407bf59de9fc26adc1c08e5304b3a4f0923fe8a3e9ad35513e",
    "Created": "2025-10-14T23:54:09.750401976+09:00",
    "Scope": "local",
    "Driver": "bridge",
    "EnableIPv4": true,
    "EnableIPv6": true,
    "IPAM": {
      "Driver": "default",
      "Options": {},
      "Config": [
        {
          "Subnet": "fc00:f853:ccd:e793::/64"
        },
        {
          "Subnet": "172.18.0.0/16",
          "Gateway": "172.18.0.1"
        }
      ]

 

 

3장. 컨테이너

3.1 Docker를 이용한 이미지 빌드

컨테이너 이미지는 계층구조이며, Dockerfile은 이미지를 생성하기위한 명령어의 집합이라고 생각하면된다. 실습은 아까 git clone해놓은 소스로 진행

~/chapters/chapters/ch03/python-app$ ls
Dockerfile  app.py  requirements.txt
cat Dockerfile
FROM registry.access.redhat.com/ubi8/python-39
ENV PORT 8080
EXPOSE 8080
WORKDIR /usr/src/app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

ENTRYPOINT ["python"]

 

docker inspect $MYUSER/pythonapp:latest를 통해 확인해보면 아래와 같이 레이어가 8개임을 알수있다.

            "Layers": [
                "sha256:efbb01c414da9dbe80503875585172034d618260b0179622a67440af141ada49",
                "sha256:0e770dacd8dd8e0f783addb2f8e2889360ecc1443acc1ca32f03158f8b459b14",
                "sha256:88e9a9d0ae01242e58df8949eead610a48a097424286ea4b79578d4715a42a43",
                "sha256:f279945932ccc151ce3a30913fcee204cdd7c22812445100ecafde8a9d59ee90",
                "sha256:9a68b585f4d06dded75f1f9b9ef570d2a90c2d4f5fb0c593e27a7e47e0950980",
                "sha256:b714f69a1cb44a6dad57da75d65cdc810e493b91e7e2abfa55e56824729eefb9",
                "sha256:f169d82343d6db538cb505124b9bca4bb541d1646039da7ef53c82d366887392",
                "sha256:fc9e577685d62ded67ed62e0053c28c386a774bf7881e6eefa94899acba34392"
            ]

 

이는 Dockerfile에 작성되어있는 FROM registry.access.redhat.com/ubi8/python-39 이 레이어4개로 구성되어있어서 총 8개로 보이는것이다. 해당 이미지를 docker inspect로 확인해보면 구성된 레이어가 위에 8개중 상단4개와 동일함을 알수있다.

 

     docker inspect registry.access.redhat.com/ubi8/python-39:latest | jq
     ...중략....
      "Layers": [
        "sha256:efbb01c414da9dbe80503875585172034d618260b0179622a67440af141ada49",
        "sha256:0e770dacd8dd8e0f783addb2f8e2889360ecc1443acc1ca32f03158f8b459b14",
        "sha256:88e9a9d0ae01242e58df8949eead610a48a097424286ea4b79578d4715a42a43",
        "sha256:f279945932ccc151ce3a30913fcee204cdd7c22812445100ecafde8a9d59ee90"
      ]

docker history $MYUSER/pythonapp:latest
IMAGE          CREATED              CREATED BY                                      SIZE      COMMENT
bb2d2da792fb   About a minute ago   CMD ["app.py"]                                  0B        buildkit.dockerfile.v0
<missing>      About a minute ago   ENTRYPOINT ["python"]                           0B        buildkit.dockerfile.v0
<missing>      About a minute ago   COPY . . # buildkit                             404B      buildkit.dockerfile.v0
<missing>      About a minute ago   RUN /bin/sh -c pip install --no-cache-dir -r…   4.05MB    buildkit.dockerfile.v0
<missing>      About a minute ago   COPY requirements.txt ./ # buildkit             5B        buildkit.dockerfile.v0
<missing>      About a minute ago   WORKDIR /usr/src/app                            0B        buildkit.dockerfile.v0
<missing>      About a minute ago   EXPOSE [8080/tcp]                               0B        buildkit.dockerfile.v0
<missing>      About a minute ago   ENV PORT=8080                                   0B        buildkit.dockerfile.v0

 

docker push $MYREGISTRY/$MYUSER/pythonapp:latest
The push refers to repository [docker.io/zeroone5727/pythonapp]
fc9e577685d6: Pushed
f169d82343d6: Pushed
b714f69a1cb4: Pushed
9a68b585f4d0: Pushed
f279945932cc: Pushed
88e9a9d0ae01: Pushed
0e770dacd8dd: Pushed
efbb01c414da: Pushed
latest: digest: sha256:539e05b6d7aa958d21742efc5542cccc78273c226ef4663fca0065b709359df6 size: 1999

 

 

푸시된 이미지를 실행하고 해당 컨테이너 로그를 확인

 

docker run -d --name myweb -p 8080:8080 -it $MYREGISTRY/$MYUSER/pythonapp:latest

docker logs myweb
 * Serving Flask app 'app'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:8080
 * Running on http://172.17.0.2:8080
Press CTRL+C to quit
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 764-598-955
172.17.0.1 - - [14/Oct/2025 16:30:23] "GET / HTTP/1.1" 200 -
172.17.0.1 - - [14/Oct/2025 16:30:23] "GET /favicon.ico HTTP/1.1" 404 -

 

3.2 도커가 필요 없는 Jib을 사용한 컨테이너 빌드

Dockerfile을 작성하지않아도되는점이 큰장점이지만 java기반에서만 사용가능하다는 단점이있다.

WSL2환경에서의 jib실습환경구축

 

root@myk8s-worker:/# java -version
openjdk version "17.0.16" 2025-07-15
OpenJDK Runtime Environment (build 17.0.16+8-Debian-1deb12u1)
OpenJDK 64-Bit Server VM (build 17.0.16+8-Debian-1deb12u1, mixed mode, sharing)

mvn -version
Apache Maven 3.8.7
Maven home: /usr/share/maven
Java version: 17.0.16, vendor: Debian, runtime: /usr/lib/jvm/java-17-openjdk-amd64
Default locale: en_US, platform encoding: ANSI_X3.4-1968
OS name: "linux", version: "6.6.87.2-microsoft-standard-wsl2", arch: "amd64", family: "unix"

mvn compile com.google.cloud.tools:jib-maven-plugin:3.4.6:build   -Dimage=docker.io/zeroone5727/jib-example:latest   -Djib.to.auth.username=zeroone5727   -Djib.to.auth.password=""   -Djib.from.platforms=linux/amd64

 

 

docker run -d --name myweb2 -p 8080:8080 -it docker.io/zeroone5727/jib-example
Unable to find image 'zeroone5727/jib-example:latest' locally
latest: Pulling from zeroone5727/jib-example
4b3ffd8ccb52: Pull complete
0b2668578c63: Pull complete
0b04dbdb2b3f: Pull complete
fd4132a90b04: Pull complete
43cbcd26df05: Pull complete
80179ad604dc: Pull complete
6d6cf0398708: Pull complete
d2bcf32f8182: Pull complete
d3589b3a4a64: Pull complete
Digest: sha256:63b47bedc99cb91c3e690d4942f4608045534078b992c04374f4cde40601ae9a
Status: Downloaded newer image for zeroone5727/jib-example:latest
a4f8a5cf3461ec62c81b98c93451d644cd0138636449f690f3f00b60ffaea938

curl -s 127.0.0.1:8080/hello | jq
{
  "id": 1,
  "content": "Hello, World!"
}
root@4:~# docker images
REPOSITORY                                  TAG       IMAGE ID       CREATED          SIZE
zeroone5727/pythonapp                       latest    bb2d2da792fb   32 minutes ago   878MB
registry.access.redhat.com/ubi8/python-39   latest    a160f0c65897   11 hours ago     874MB
kindest/node                                <none>    4357c93ef232   6 weeks ago      985MB
kindest/node                                v1.32.8   05f8e4e76c17   6 weeks ago      1.04GB
zeroone5727/jib-example                     latest    2574b2ca2237   55 years ago     281MB
root@4:~# docker inspect zeroone5727/jib-example | jq
[
  {
    "Id": "sha256:2574b2ca2237d60e4a63e86ed4883e1306e369e824937cd6587857ec9fc3eb4c",
    "RepoTags": [

 

3.3 빌다(Buildah)를 사용한 컨테이너빌드

*빌다는 Mac에서는 테스트가 불가능하다.(리눅스하위시스템이 반드시필요)

컨테이너 내부 podman설치 및 버젼확인하기

 

(⎈|BINARY-N/A:N/A) zosys@4:~/chapters/chapters/ch03$ docker exec -it myk8s-control-plane bash

root@myk8s-control-plane:/# apt update
Get:1 http://deb.debian.org/debian bookworm InRelease [151 kB]
Get:2 http://deb.debian.org/debian bookworm-updates InRelease [55.4 kB]
Get:3 http://deb.debian.org/debian-security bookworm-security InRelease [48.0 kB]


----중략---------
root@myk8s-control-plane:/# apt install podman -y
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:

----중략---------
root@myk8s-control-plane:/# podman version
Client:       Podman Engine
Version:      4.3.1
API Version:  4.3.1
Go Version:   go1.19.8
Built:        Thu Jan  1 00:00:00 1970
OS/Arch:      linux/amd64


root@myk8s-control-plane:/# buildah version
Version:         1.28.2
Go Version:      go1.19.8
Image Spec:      1.1.0-rc2
Runtime Spec:    1.0.2-dev
CNI Spec:        1.0.0
libcni Version:
image Version:   5.23.1
Git Commit:
Built:           Thu Jan  1 00:00:00 1970
OS/Arch:         linux/amd64
BuildPlatform:   linux/amd64

 

빌다 이미지 빌드 및 파드맨 확인

root@myk8s-control-plane:/# buildah from centos:latest
Resolved "centos" as an alias (/etc/containers/registries.conf.d/shortnames.conf)
Trying to pull quay.io/centos/centos:latest...
Getting image source signatures
Copying blob bf573055768d done
Copying config 4fbde73dcc done
Writing manifest to image destination
Storing signatures
centos-working-container

root@myk8s-control-plane:/# buildah images
REPOSITORY              TAG      IMAGE ID       CREATED      SIZE
quay.io/centos/centos   latest   4fbde73dcc66   2 days ago   316 MB

#이미지 빌드를 위한 빌다의 전용 컨테이너
root@myk8s-control-plane:/# buildah containers
CONTAINER ID  BUILDER  IMAGE ID     IMAGE NAME                       CONTAINER NAME
ffac44263205     *     4fbde73dcc66 quay.io/centos/centos:latest     centos-working-container

root@myk8s-control-plane:/# buildah run centos-working-container yum install -y httpd
CentOS Stream 10 - BaseOS                                                               3.2 MB/s | 6.8 MB     00:02
CentOS Stream 10 - AppStream                                                            2.6 MB/s | 3.4 MB     00:01
CentOS Stream 10 - Extras packages                                                      9.9 kB/s | 7.3 kB     00:00
Dependencies resolved.
========================================================================================================================
 Package                           Architecture          Version                         Repository                Size
========================================================================================================================
Installing:
-------------------------중략----------------------------

root@myk8s-control-plane:/# cat << EOF > index.html
<html>
    <head>
        <title>Cloudneta CICD Study GOGO</title>
    </head>
    <body>
        <h1>Hello, World!</h1>
    </body>
</html>
EOF

root@myk8s-control-plane:/# buildah copy centos-working-container index.html /var/www/html/index.html
48dc53b65b887ca53f119b8098e06f39dd107d202c8d5e78f6406cf7b7d50123

root@myk8s-control-plane:/# buildah config --entrypoint "/usr/sbin/httpd -DFOREGROUND" centos-working-container
WARN[0000] cmd "/bin/bash" exists but will be ignored because of entrypoint settings

root@myk8s-control-plane:/# buildah commit centos-working-container docker.io/zeroone5727/gitops-website
Getting image source signatures
Copying blob 288e9c66457d skipped: already exists
Copying blob bd8d16be1d7e done
Copying config 0558748eaf done
Writing manifest to image destination
Storing signatures
0558748eaf4d4fdd32ed525aad5ab88bdb1e8f2e4e9bd7308ff9ec4416317fdf

root@myk8s-control-plane:/# podman run --runtime /usr/local/sbin/runc -d --name myweb -p 8080:80 -it docker.io/zeroone5727/gitops-website
fc007ee6906f12028494b4e24d7ec092bf4e0107c5847fab563f7a71a19d8f9d
root@myk8s-control-plane:/# podman ps
CONTAINER ID  IMAGE                                        COMMAND     CREATED        STATUS            PORTS                 NAMES
fc007ee6906f  docker.io/zeroone5727/gitops-website:latest  /bin/bash   3 seconds ago  Up 3 seconds ago  0.0.0.0:8080->80/tcp  myweb

root@myk8s-control-plane:/# curl -s localhost:8080
<html>
    <head>
        <title>Cloudneta CICD Study GOGO</title>
    </head>
    <body>
        <h1>Hello, World!</h1>
    </body>
</html>

 

Dockerfile로 buildah 이미지 빌드해보기

oot@myk8s-control-plane:/# cat << EOF > Dockerfile
FROM centos:latest
RUN yum -y install httpd
COPY index.html /var/www/html/index.html
EXPOSE 80
CMD ["/usr/sbin/httpd", "-DFOREGROUND"]
EOF

root@myk8s-control-plane:/# buildah build -f Dockerfile -t docker.io/zeroone5727/gitops-website
STEP 1/5: FROM centos:latest
STEP 2/5: RUN yum -y install httpd
CentOS Stream 10 - BaseOS                       3.0 MB/s | 6.8 MB     00:02
CentOS Stream 10 - AppStream                    1.3 MB/s | 3.4 MB     00:02
CentOS Stream 10 - Extras packages              7.7 kB/s | 7.3 kB     00:00
Dependencies resolved.
================================================================================
 Package                 Arch        Version               Repository      Size
================================================================================
Installing:
 httpd                   x86_64      2.4.63-5.el10         appstream       49 k
---------------------중략-------------------------


root@myk8s-control-plane:/# buildah images
REPOSITORY                             TAG      IMAGE ID       CREATED          SIZE
docker.io/zeroone5727/gitops-website   latest   7db81e718f5b   30 seconds ago   361 MB

 

이미지를 도커레지스트리에 push해보기

root@myk8s-control-plane:/# buildah login --username zeroone5727 docker.io
Password:
Login Succeeded!

root@myk8s-control-plane:/# buildah push 7db81e718f5b docker.io/zeroone5727/gitops-website
Getting image source signatures
Copying blob 0209c55c26e9 done
Copying blob 288e9c66457d done
Copying config 7db81e718f done
Writing manifest to image destination
Storing signatures

 

 

3.4 빌드팩을 통한 컨테이너 빌드

빌드팩은 두가지 단계로 진행된다

  1. 탐지
  2. 빌드팩은 소스 코드를 탐색하여 프로그래밍 언어나 프레임워크를 무엇을 사용하는지 파악하고 해당 소스 코드 빌드에 가장 적합한 빌드팩을 선정한다.
  3. 빌드
  4. 빌드팩이 결정되면 소스는 컴파일되고, 빌드팩은 엔트리포인트와 시작 스크립트가 포함된 컨테이너 이미지를 만든다.

빌드팩 설치하기(WSL2 Ubuntu)

root@4:~# sudo add-apt-repository ppa:cncf-buildpacks/pack-cli
Repository: 'Types: deb
URIs: https://ppa.launchpadcontent.net/cncf-buildpacks/pack-cli/ubuntu/
Suites: noble
Components: main
'
Description:
CLI for building apps using Cloud Native Buildpacks
More info: https://launchpad.net/~cncf-buildpacks/+archive/ubuntu/pack-cli
Adding repository.
Press [ENTER] to continue or Ctrl-c to cancel.

root@4:~# apt-get install -y pack-cli
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done

root@4:~# pack version
0.38.2
root@4:~# pack --help
CLI for building apps using Cloud Native Buildpacks

Usage:
  pack [command]

 

nodejs샘플 빌드팩으로 이미지 만들어보기(이미지 생성시에 빌더 지정)

(⎈|BINARY-N/A:N/A) zosys@4:~/chapters/chapters/ch03/nodejs-app$ sudo pack builder suggest
Suggested builders:
        Google:                gcr.io/buildpacks/builder:google-22                     Ubuntu 22.04 base image with buildpacks for .NET, Dart, Go, Java, Node.js, PHP, Python, and Ruby                                                         
        Heroku:                heroku/builder:24                                       Ubuntu 24.04 AMD64+ARM64 base image with buildpacks for .NET, Go, Java, Node.js, PHP, Python, Ruby & Scala.                                              
        Paketo Buildpacks:     paketobuildpacks/builder-jammy-base                     Ubuntu 22.04 Jammy Jellyfish base image with buildpacks for Java, Go, .NET Core, Node.js, Python, Apache HTTPD, NGINX and Procfile                       
        Paketo Buildpacks:     paketobuildpacks/builder-jammy-buildpackless-static     Static base image (Ubuntu Jammy Jellyfish build image, distroless-like run image) with no buildpacks included. To use, specify buildpacks at build time. 
        Paketo Buildpacks:     paketobuildpacks/builder-jammy-full                     Ubuntu 22.04 Jammy Jellyfish full image with buildpacks for Apache HTTPD, Go, Java, Java Native Image, .NET, NGINX, Node.js, PHP, Procfile, Python, and Ruby
        Paketo Buildpacks:     paketobuildpacks/builder-jammy-tiny                     Tiny base image (Ubuntu Jammy Jellyfish build image, distroless-like run image) with buildpacks for Java, Java Native Image and Go                       
        Paketo Buildpacks:     paketobuildpacks/builder-ubi8-base                      Ubi 8 base builder with buildpacks for Node.js, Java, Quarkus and Procfile                                                                               

Tip: Learn more about a specific builder with:
        pack builder inspect <builder-image>

(⎈|BINARY-N/A:N/A) zosys@4:~/chapters/chapters/ch03/nodejs-app$ sudo pack build nodejs-app --builder paketobuildpacks/builder-jammy-base
latest: Pulling from paketobuildpacks/builder-jammy-base

(⎈|BINARY-N/A:N/A) zosys@4:~/chapters/chapters/ch03/nodejs-app$ docker images
REPOSITORY                                  TAG       IMAGE ID       CREATED        SIZE
paketobuildpacks/run-jammy-base             latest    d255418a9350   11 hours ago   111MB
zeroone5727/pythonapp                       latest    bb2d2da792fb   46 hours ago   878MB
registry.access.redhat.com/ubi8/python-39   latest    a160f0c65897   2 days ago     874MB
kindest/node                                <none>    4357c93ef232   7 weeks ago    985MB
kindest/node                                v1.32.8   05f8e4e76c17   7 weeks ago    1.04GB
paketobuildpacks/builder-jammy-base         latest    58ba6d7beab5   45 years ago   2.5GB
nodejs-app                                  latest    2aee05010faa   45 years ago   374MB

(⎈|BINARY-N/A:N/A) zosys@4:~/chapters/chapters/ch03/nodejs-app$ docker inspect nodejs-app | jq | more
[
  {
    "Id": "sha256:2aee05010faa0effb34b6443be5cbab411a1768c00f5eb6b43d5bec9510bbcfd",
    "RepoTags": [
      "nodejs-app:latest"
    ],
    "RepoDigests": [],
    "Parent": "",
    "Comment": "",
    "Created": "1980-01-01T00:00:01Z",
    "DockerVersion": "",
    "Author": "",
    "Architecture": "amd64",
    "Os": "linux",
    "Size": 374370403,
    "GraphDriver": {
--------중략------------

(⎈|BINARY-N/A:N/A) zosys@4:~/chapters/chapters/ch03/nodejs-app$ docker run -d --name myapp --rm -p 3000:3000 nodejs-app
41ffa68b998ef52cfe88afaae8b5ed95678cc6289fe2542563208ea88b2f4739
(⎈|BINARY-N/A:N/A) zosys@4:~/chapters/chapters/ch03/nodejs-app$ docker ps -a
CONTAINER ID   IMAGE                  COMMAND                  CREATED         STATUS             PORTS                                                             NAMES
41ffa68b998e   nodejs-app             "/cnb/process/web"       2 seconds ago   Up 1 second        0.0.0.0:3000->3000/tcp, [::]:3000->3000/tcp                       myapp

 

3.5 십라이트 빌다 카니코를 이용한 이미지 빌드 (쿠버네티스이용)

쿠버네티스는 이미지 빌드를 지원하지않으며, 그 대체제로 사용되는것이 CI/CD툴 또는 빌드관리 프레임워크가 있는데, 그중 십라이트라는 프레임워크를 사용해본다.

kind K8s에 십라이트설치

root@myk8s-control-plane:/# kubectl apply -f https://storage.googleapis.com/tekton-releases/pipeline/previous/v0.70.0/release.yaml
\^Hnamespace/tekton-pipelines created
-----------중략------------------

root@myk8s-control-plane:/# kubectl get crd
NAME                                       CREATED AT
clustertasks.tekton.dev                    2025-10-16T15:03:37Z
customruns.tekton.dev                      2025-10-16T15:03:38Z
pipelineruns.tekton.dev                    2025-10-16T15:03:38Z
pipelines.tekton.dev                       2025-10-16T15:03:38Z
resolutionrequests.resolution.tekton.dev   2025-10-16T15:03:38Z
stepactions.tekton.dev                     2025-10-16T15:03:38Z
taskruns.tekton.dev                        2025-10-16T15:03:38Z
tasks.tekton.dev                           2025-10-16T15:03:38Z
verificationpolicies.tekton.dev            2025-10-16T15:03:38Z
root@myk8s-control-plane:/# kubectl get sa -n tekton-pipelines
NAME                          SECRETS   AGE
default                       0         34s
tekton-events-controller      0         34s
tekton-pipelines-controller   0         34s
tekton-pipelines-webhook      0         34s

root@myk8s-control-plane:/# kubectl apply -f https://github.com/shipwright-io/build/releases/download/v0.11.0/release.yaml
namespace/shipwright-build created
role.rbac.authorization.k8s.io/shipwright-build-controller created
clusterrole.rbac.authorization.k8s.io/shipwright-build-controller created
clusterrolebinding.rbac.authorization.k8s.io/shipwright-build-controller created
rolebinding.rbac.authorization.k8s.io/shipwright-build-controller created
serviceaccount/shipwright-build-controller created
deployment.apps/shipwright-build-controller created
clusterrole.rbac.authorization.k8s.io/shipwright-build-aggregate-edit created
clusterrole.rbac.authorization.k8s.io/shipwright-build-aggregate-view created
customresourcedefinition.apiextensions.k8s.io/buildruns.shipwright.io created
customresourcedefinition.apiextensions.k8s.io/builds.shipwright.io created
customresourcedefinition.apiextensions.k8s.io/buildstrategies.shipwright.io created
customresourcedefinition.apiextensions.k8s.io/clusterbuildstrategies.shipwright.io created

root@myk8s-control-plane:/# kubectl apply -f https://github.com/shipwright-io/build/releases/download/v0.11.0/sample-strategies.yaml
clusterbuildstrategy.shipwright.io/buildah created
clusterbuildstrategy.shipwright.io/buildkit created
clusterbuildstrategy.shipwright.io/buildpacks-v3-heroku created
clusterbuildstrategy.shipwright.io/buildpacks-v3 created
clusterbuildstrategy.shipwright.io/kaniko-trivy created
clusterbuildstrategy.shipwright.io/kaniko created
clusterbuildstrategy.shipwright.io/ko created
clusterbuildstrategy.shipwright.io/source-to-image-redhat created
clusterbuildstrategy.shipwright.io/source-to-image created
root@myk8s-control-plane:/# kubectl get-all -n shipwright-build


root@myk8s-control-plane:/# kubectl get clusterbuildstrategy
NAME                     AGE
buildah                  117s
buildkit                 117s
buildpacks-v3            117s
buildpacks-v3-heroku     117s
kaniko                   117s
kaniko-trivy             117s
ko                       117s
source-to-image          117s
source-to-image-redhat   117s

root@myk8s-control-plane:/# kubectl get clusterbuildstrategy buildah -o yaml
apiVersion: shipwright.io/v1alpha1
kind: ClusterBuildStrategy
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"shipwright.io/v1alpha1","kind":"ClusterBuildStrategy","metadata":{"annotations":{},"name":"buildah"},"spec":{"buildSteps":[{"args":["-c","set -euo pipefail\n

 

시크릿생성 (생성안할경우 아래와 같이 에러발생함)

root@myk8s-control-plane:/# k get build
NAME                  REGISTERED   REASON                        BUILDSTRATEGYKIND      BUILDSTRATEGYNAME   CREATIONTIME
kaniko-golang-build   False        SpecOutputSecretRefNotFound   ClusterBuildStrategy   kaniko              6m3s


root@myk8s-control-plane:/# kubectl create secret docker-registry push-secret \
--docker-server=$REGISTRY_SERVER \
--docker-username=$REGISTRY_USER \
--docker-password=$REGISTRY_PASSWORD \
--docker-email=$EMAIL

root@myk8s-control-plane:/# k get secret
NAME          TYPE                             DATA   AGE
push-secret   kubernetes.io/dockerconfigjson   1      48s

 

빌드객체 생성 및 빌드런객체생성하여 파드생성

# Build 객체 생성
cat <<EOF | kubectl apply -f -
apiVersion: shipwright.io/v1alpha1
kind: Build
metadata:
  name: kaniko-golang-build
  annotations:
    build.shipwright.io/build-run-deletion: "true"   # 빌드 완료 후 BuildRun 삭제
spec:
  source:
    url: https://github.com/shipwright-io/sample-go  # 소스 코드를 가져올 저장소
    contextDir: docker-build                         # 소스 코드가 있는 디렉터리
  strategy:
    name: kaniko                                     # 빌드에 사용할 ClusterBuildStrategy 이름
    kind: ClusterBuildStrategy
  dockerfile: Dockerfile
  output:
    image: docker.io/$REGISTRY_USER/sample-golang:latest # 결과 이미지를 저장할 장소
    credentials:
      name: push-secret                              # 레지스트리에 인증하고 이미지를 푸시하는 데 사용할 시크릿 이름  
EOF


root@myk8s-control-plane:/# kubectl create -f buildrun-go.yaml
buildrun.shipwright.io/kaniko-golang-buildrun-6zphv created

cat << EOF > buildrun-go.yaml
apiVersion: shipwright.io/v1alpha1
kind: BuildRun
metadata:
  generateName: kaniko-golang-buildrun-
spec:
  buildRef:
    name: kaniko-golang-build
EOF

root@myk8s-control-plane:/# k get build
NAME                  REGISTERED   REASON      BUILDSTRATEGYKIND      BUILDSTRATEGYNAME   CREATIONTIME
kaniko-golang-build   True         Succeeded   ClusterBuildStrategy   kaniko              6m17s

root@myk8s-control-plane:/# k get pod -n default -w
NAME                                     READY   STATUS     RESTARTS   AGE
kaniko-golang-buildrun-6zphv-gf5d4-pod   0/3     Init:1/2   0          11s
kaniko-golang-buildrun-6zphv-gf5d4-pod   0/3     PodInitializing   0          11s
-------중략-----------------

 

 

root@myk8s-control-plane:/# kubectl describe pod -l clusterbuildstrategy.shipwright.io/name=kaniko | more
Name:             kaniko-golang-buildrun-6zphv-gf5d4-pod
Namespace:        default
Priority:         0
Service Account:  default
Node:             myk8s-worker/172.18.0.4
Start Time:       Thu, 16 Oct 2025 15:25:42 +0000
Labels:           app.kubernetes.io/managed-by=tekton-pipelines
                  build.shipwright.io/generation=2
                  build.shipwright.io/name=kaniko-golang-build
                  buildrun.shipwright.io/generation=1
                  buildrun.shipwright.io/name=kaniko-golang-buildrun-6zphv
                  clusterbuildstrategy.shipwright.io/generation=1
                  clusterbuildstrategy.shipwright.io/name=kaniko
                  tekton.dev/taskRun=kaniko-golang-buildrun-6zphv-gf5d4
                  tekton.dev/taskRunUID=99cfed13-b622-4ba2-9707-a4c1
                  
root@myk8s-control-plane:/# kubectl get buildruns.shipwright.io
NAME                           SUCCEEDED   REASON      STARTTIME   COMPLETIONTIME
kaniko-golang-buildrun-6zphv   True        Succeeded   19m         16m

 

4장 커스터마이즈

  • 쿠버네티스 배포는 결국 YAML 파일을 클러스터에 적용하는 단순한 작업이다.
  • 어려운 부분은 YAML의 첫 버전을 만드는 초기 개발 단계다.
  • 이후 환경별 설정 파일로 변경 사항만 관리하는 도구(커스터마이즈)를 사용하면 실수와 위험을 줄일 수 있다

configMapGenerator

(⎈|kind-myk8s:default) zosys@4:~$ mkdir kustomize-test
(⎈|kind-myk8s:default) zosys@4:~$ cd kustomize-test/
(⎈|kind-myk8s:default) zosys@4:~/kustomize-test$

(⎈|kind-myk8s:default) zosys@4:~/kustomize-test$ cat <<EOF > application.properties
FOO=Bar
EOF

cat <<EOF > kustomization.yaml
configMapGenerator:
- name: example-configmap-1
  files:
  - application.properties
EOF
(⎈|kind-myk8s:default) zosys@4:~/kustomize-test$ kubectl create -k ./ --dry-run=client -o yaml --save-config=false
apiVersion: v1
data:
  application.properties: |
    FOO=Bar
kind: ConfigMap
metadata:
  name: example-configmap-1-g4hk9g2ff8
  namespace: default

(⎈|kind-myk8s:default) zosys@4:~/kustomize-test$ k create -k ./ --save-config=false
configmap/example-configmap-1-g4hk9g2ff8 created
(⎈|kind-myk8s:default) zosys@4:~/kustomize-test$ k get -k ./
NAME                             DATA   AGE
example-configmap-1-g4hk9g2ff8   1      3s
(⎈|kind-myk8s:default) zosys@4:~/kustomize-test$ k describe -k ./
Name:         example-configmap-1-g4hk9g2ff8
Namespace:    default
Labels:       <none>
Annotations:  <none>

Data
====
application.properties:
----
FOO=Bar



BinaryData
====

 

Custormizing

(⎈|kind-myk8s:default) zosys@4:~/kustomize-test$ cat << EOF > deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
EOF


(⎈|kind-myk8s:default) zosys@4:~/kustomize-test$ cat << EOF > increase_replicas.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  replicas: 3
EOF

(⎈|kind-myk8s:default) zosys@4:~/kustomize-test$ cat << EOF > set_memory.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  template:
    spec:
      containers:
      - name: my-nginx
        resources:
          limits:
            memory: 512Mi
EOF

(⎈|kind-myk8s:default) zosys@4:~/kustomize-test$ kubectl create -k ./ --dry-run=client -o yaml --save-config=false
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      run: my-nginx
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - image: nginx:alpine
        name: my-nginx
        ports:
        - containerPort: 80
        resources:
          limits:
            memory: 512Mi

(⎈|kind-myk8s:default) zosys@4:~/kustomize-test$ k get -k ./
NAME       READY   UP-TO-DATE   AVAILABLE   AGE
my-nginx   3/3     3            3           11s

(⎈|kind-myk8s:default) zosys@4:~/kustomize-test$ k describe -k ./
Name:                   my-nginx
Namespace:              default
CreationTimestamp:      Fri, 17 Oct 2025 01:17:42 +0900
Labels:                 <none>
Annotations:            deployment.kubernetes.io/revision: 1
Selector:               run=my-nginx
Replicas:               3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  run=my-nginx
  Containers:
   my-nginx:
    Image:      nginx:alpine
    Port:       80/TCP
    Host Port:  0/TCP
    Limits:
      memory:      512Mi

 

Drangonfly

대규모의 이미지를 자주 다운 받을때 배포 효율성을 높이는 이미지 가속 솔루션(P2P방식)

구성요소는 관리자와 스케쥴러 시드피어 피어로 구성되어있다.

QuickStart

(⎈|kind-myk8s:default) zosys@4:~/kustomize-test$ docker pull dragonflyoss/scheduler:latest
docker pull dragonflyoss/manager:latest
docker pull dragonflyoss/client:latest
docker pull dragonflyoss/dfinit:latest
docker images

REPOSITORY                                  TAG       IMAGE ID       CREATED        SIZE
dragonflyoss/client                         latest    2e88f9c8964a   4 hours ago    749MB
dragonflyoss/dfinit                         latest    21578e98cbbf   4 hours ago    83.1MB
dragonflyoss/manager                        latest    112c77dbd763   8 hours ago    142MB
dragonflyoss/scheduler                      latest    beea9d38465f   8 hours ago    104MB


(⎈|kind-myk8s:default) zosys@4:~/kustomize-test$ cat << EOF > charts-config.yaml
manager:
  image:
    repository: dragonflyoss/manager
    tag: latest
  metrics:
    enable: true

scheduler:
  image:
    repository: dragonflyoss/scheduler
    tag: latest
  metrics:
    enable: true

seedClient:
  image:
    repository: dragonflyoss/client
    tag: latest
  metrics:
    enable: true

client:
  image:
    repository: dragonflyoss/client
EOF           capabilities: ['pull', 'resolve']io
(⎈|kind-myk8s:default) zosys@4:~/kustomize-test$ helm repo add dragonfly https://dragonflyoss.github.io/helm-charts/
"dragonfly" has been added to your repositories
(⎈|kind-myk8s:default) zosys@4:~/kustomize-test$ helm install --wait --create-namespace --namespace dragonfly-system dragonfly dragonfly/dragonfly -f charts-config.yaml


(⎈|kind-myk8s:default) zosys@4:~/kustomize-test$ helm install --wait --create-namespace --namespace dragonfly-system dragonfly dragonfly/dragonfly -f charts-config.yaml
NAME: dragonfly
LAST DEPLOYED: Fri Oct 17 01:37:44 2025
NAMESPACE: dragonfly-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
1. Get the manager address by running these commands:
  export MANAGER_POD_NAME=$(kubectl get pods --namespace dragonfly-system -l "app=dragonfly,release=dragonfly,component=manager" -o jsonpath={.items[0].metadata.name})
  export MANAGER_CONTAINER_PORT=$(kubectl get pod --namespace dragonfly-system $MANAGER_POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
  kubectl --namespace dragonfly-system port-forward $MANAGER_POD_NAME 8080:$MANAGER_CONTAINER_PORT
  echo "Visit http://127.0.0.1:8080 to use your manager"

2. Get the scheduler address by running these commands:
  export SCHEDULER_POD_NAME=$(kubectl get pods --namespace dragonfly-system -l "app=dragonfly,release=dragonfly,component=scheduler" -o jsonpath={.items[0].metadata.name})
  export SCHEDULER_CONTAINER_PORT=$(kubectl get pod --namespace dragonfly-system $SCHEDULER_POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
  kubectl --namespace dragonfly-system port-forward $SCHEDULER_POD_NAME 8002:$SCHEDULER_CONTAINER_PORT
  echo "Visit http://127.0.0.1:8002 to use your scheduler"


(⎈|kind-myk8s:default) zosys@4:~/kustomize-test$ kubectl get deploy,ds,sts -n dragonfly-system
NAME                                READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/dragonfly-manager   3/3     3            3           7m56s

NAME                              DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
daemonset.apps/dragonfly-client   1         1         1       1            1           <none>          7m56s

NAME                                        READY   AGE
statefulset.apps/dragonfly-mysql            1/1     7m56s
statefulset.apps/dragonfly-redis-master     1/1     7m56s
statefulset.apps/dragonfly-redis-replicas   3/3     7m56s
statefulset.apps/dragonfly-scheduler        3/3     7m56s
statefulset.apps/dragonfly-seed-client      3/3     7m56s

(⎈|kind-myk8s:default) zosys@4:~/kustomize-test$ kubectl get pod -n dragonfly-system
NAME                                 READY   STATUS    RESTARTS        AGE
dragonfly-client-bn74x               1/1     Running   0               8m9s
dragonfly-manager-6dc775cccb-jgjgw   1/1     Running   0               8m9s
dragonfly-manager-6dc775cccb-jxnkz   1/1     Running   0               8m9s
dragonfly-manager-6dc775cccb-nlfqs   1/1     Running   0               8m9s
dragonfly-mysql-0                    1/1     Running   0               8m9s
dragonfly-redis-master-0             1/1     Running   0               8m9s
dragonfly-redis-replicas-0           1/1     Running   1 (7m4s ago)    8m9s
dragonfly-redis-replicas-1           1/1     Running   0               6m38s
dragonfly-redis-replicas-2           1/1     Running   0               6m9s
dragonfly-scheduler-0                1/1     Running   0               8m9s
dragonfly-scheduler-1                1/1     Running   0               5m26s
dragonfly-scheduler-2                1/1     Running   0               5m13s
dragonfly-seed-client-0              1/1     Running   2 (5m44s ago)   8m9s
dragonfly-seed-client-1              1/1     Running   0               5m
dragonfly-seed-client-2              1/1     Running   0               4m23s

실습환경 구성

kind create cluster --name myk8s --image kindest/node:v1.33.4 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 30000 
    hostPort: 30000
  - containerPort: 30001
    hostPort: 30001
  - containerPort: 30002
    hostPort: 30002
  - containerPort: 30003
    hostPort: 30003    
EOF

# minio-operator : https://github.com/minio/operator/blob/master/helm/operator/values.yaml
helm install --namespace minio-operator --create-namespace minio-operator minio-operator/operator --set operator.replicaCount=1
kubectl get all -n minio-operator

# tenant values : https://github.com/minio/operator/blob/master/helm/tenant/values.yaml
cat << EOF > minio-tenant-1-values.yaml
tenant:
  name: tenant1

  configSecret:
    name: tenant1-env-configuration
    accessKey: minio
    secretKey: minio123

  pools:
    - servers: 1
      name: pool-0
      volumesPerServer: 4
      size: 1Gi 
      storageClassName: standard
  env:
    - name: MINIO_STORAGE_CLASS_STANDARD
      value: "EC:1"
    - name: MINIO_PROMETHEUS_AUTH_TYPE
      value: public

  features:
    bucketDNS: true
    
  metrics:
    enabled: true
    port: 9000
    protocol: http
EOF
helm install --namespace tenant1 --create-namespace --values minio-tenant-1-values.yaml tenant1 minio-operator/tenant \
 && kubectl get tenants -A -w

#
kubectl describe tenants -n tenant1
kubectl get pvc -n tenant1
kubectl describe pvc -n tenant1
kubectl get sts,pod,svc,ep,pvc,secret -n tenant1
kubectl get pod -n tenant1 -l v1.min.io/pool=pool-0 -owide
kubectl describe pod -n tenant1 -l v1.min.io/pool=pool-0
kubectl stern -n tenant1 -l v1.min.io/pool=pool-0
kubectl exec -it -n tenant1 sts/tenant1-pool-0 -c minio -- id
kubectl exec -it -n tenant1 sts/tenant1-pool-0 -c minio -- env
kubectl exec -it -n tenant1 sts/tenant1-pool-0 -c minio -- cat /tmp/minio/config.env
kubectl get secret -n tenant1 tenant1-env-configuration -o jsonpath='{.data.config\.env}' | base64 -d ; echo
kubectl get secret -n tenant1 tenant1-tls -o jsonpath='{.data.public\.crt}' | base64 -d
kubectl get secret -n tenant1 tenant1-tls -o jsonpath='{.data.public\.crt}' | base64 -d | openssl x509 -noout -text
     
#
kubectl patch svc -n tenant1 tenant1-console -p '{"spec": {"type": "NodePort", "ports": [{"port": 9443, "targetPort": 9443, "nodePort": 30001}]}}'
kubectl patch svc -n tenant1 minio -p '{"spec": {"type": "NodePort", "ports": [{"port": 443, "targetPort": 9000, "nodePort": 30002}]}}'

# 기본키(minio , minio123)
open "https://127.0.0.1:30001"

# mc alias
mc alias set k8s-tenant1 https://127.0.0.1:30002 minio minio123 --insecure
mc alias list
mc admin info k8s-tenant1 --insecure

# alias
MYALIAS=k8s-tenant1
mc ls $MYALIAS --recursive --insecure

# 신규 터미널 : 모니터링 -> mc cli 와 웹 브라우저에서 인가 출력 정보 비교
mc admin trace $MYALIAS --verbose --insecure
or
mc admin trace $MYALIAS --verbose --all --insecure

## mc cli 와 웹 브라우저에서 인가 출력 정보 비교
127.0.0.1:30002 Authorization: AWS4-HMAC-SHA256 Credential=minio/20250921/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=7a4a96a7cbe71edaa070c14843b89ce51fb24be3ab121a5f264bdba88b43c6e0

## mc cli 와 웹 브라우저에서 인가 출력 정보 비교
minio.tenant1.svc.cluster.local X-Amz-Security-Token: eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3NLZXkiOiJJSThUTlQ3UVFJMURPWktSTDVFTiIsImV4cCI6MTc1ODQ3MTc4NiwicGFyZW50IjoibWluaW8ifQ.vgrUYX7HJLHpZDg4bwXwZ255AQUXuOXkg30ov8kiS05olImLLiOz-pvR6RcFDPMsq0GTDhycse36iAxxmFqONg
minio.tenant1.svc.cluster.local Authorization: AWS4-HMAC-SHA256 Credential=II8TNT7QQI1DOZKRL5EN/20250921/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-security-token, Signature=a58c10ff7b9a6507a6b09d0cd20d87abef905048bed245443c5ac768d65ab9e2


# 버킷 생성 
mc mb $MYALIAS/mybucket --insecure
mc ls $MYALIAS --insecure

# object 업로드
echo hello > hello.txt
mc cp hello.txt $MYALIAS/mybucket --insecure

# 웹 콘솔에서 확인 후 trace 확인

버킷생성, 파일 업로드와 그에대한 로그

 

인증 

  • 클라이언트의 신원을 확인하는 과정.
  • MinIO는 AWS Signature Version 4 프로토콜을 사용하여 인증을 요구합니다.
  • 클라이언트는 유효한 Access Key와 Secret Key를 제시해야 하며,
    • S3 API 요청 (예: PUT, GET, DELETE)
    • MinIO 관리 API 요청
      모두 인증을 거쳐야만 접근 가능합니다.

인가 

  • 인증된 클라이언트가 어떤 작업과 리소스를 사용할 수 있는지 제한하는 과정.
  • PBAC (Policy-Based Access Control) 모델을 사용합니다.
  • 정책은 특정 사용자 또는 그룹에 부여되며,
    • 허용되는 S3 작업(action)
    • 특정 조건(conditions)
      등을 정의할 수 있습니다.
  • 기본 정책은 거부(Deny by default) 이므로, 정책에 명시되지 않은 작업이나 리소스는 접근할 수 없습니다.

ID 관리: 내장 관리 기능 vs 외부 관리 기능

  • 내장 ID 공급자(Minio Internal IDP)
    MinIO가 자체 내장한 ID 관리 기능으로, 사용자 계정을 직접 생성·관리하며, 사용자와 그룹에 명시적으로 정책을 연결해야 합니다. 내장 IDP 사용 시 정책 할당과 사용자·그룹 관리를 MinIO 명령어를 통해 수행합니다.
  • 외부 ID 공급자(External IDP)
    MinIO는 다음과 같은 외부 IDP 연동을 지원하여 ID 관리를 위임할 수 있습니다.
    • OpenID Connect (OIDC) 호환 서비스
    • Active Directory / LDAP
    • MinIO 인증 플러그인을 활용한 커스텀 외부 ID 관리자
      이를 통해 기존 조직의 인증 시스템과 통합할 수 있습니다.

접근 관리

  • MinIO는 정책 기반 접근 제어(PBAC)를 사용하며, 인증된 사용자에게 권한 있는 작업과 리소스만 접근하도록 정책으로 정의합니다.
  • PBAC 정책은 사용자 또는 그룹에 할당되며, 내장 IDP 사용 시 명령으로 직접 연결해야 합니다.
  • 외부 IDP 사용 시 정책 할당 방법은 IDP 종류에 따라 다릅니다.
  • MinIO의 PBAC 정책은 AWS IAM 정책 구문과 호환되도록 설계되어 있어, AWS IAM 정책 작성 문서를 참고해 정책을 작성할 수 있습니다.
  • 기본 원칙은 명시적으로 허용된 작업이나 리소스만 접근 가능하며, 명시되지 않은 요청은 기본적으로 거부됩니다 (Deny by default).
  • MinIO는 정책 기반 접근 제어(PBAC)를 이용해 사용자의 권한 있는 작업과 접근 리소스를 정의하며, 정책은 사용자 또는 그룹에 할당됩니다.
  • PBAC는 AWS IAM 정책 문법, 구조, 동작과 호환되며, 조건문을 통해 특정 태그가 있는 객체에만 액세스를 제한하는 태그 기반 정책 조건도 지원합니다.
  • 기본 제공 내장 정책으로는 consoleAdmin(전체 권한), readonly(읽기 전용), readwrite(읽기/쓰기), diagnostics(진단 권한), writeonly(쓰기 전용) 등이 있으며, 명시적 거부(Deny)가 허용(Allow)를 OVERRIDE합니다.

 

PBAC 정책 생성 / 삭제 실습

#기본 정책 확인

#
MYALIAS=k8s-tenant1
mc ls $MYALIAS --insecure

#
mc admin policy list $MYALIAS --insecure
readwrite
writeonly
consoleAdmin
diagnostics
readonly

#신규 정책 생성
#
mc admin policy info $MYALIAS readonly --insecure | jq
{
  "PolicyName": "readonly",
  "Policy": {
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Action": [
          "s3:GetBucketLocation",
          "s3:GetObject"
        ],
        "Resource": [
          "arn:aws:s3:::*"
        ]
      }
    ]
  }
}

# 기본 정책을 정책 부분만 파일로 저장
mc admin policy info $MYALIAS readonly --insecure --policy-file new.json

#
cat new.json| jq
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetBucketLocation",
        "s3:GetObject"
      ],
      "Resource": [
        "arn:aws:s3:::*"
      ]
    }
  ]
}

# Action 추가
vi new.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetBucketLocation",
        "s3:GetObject",
        "s3:List*"
      ],
      "Resource": [
        "arn:aws:s3:::*"
      ]
    }
  ]
}

# 정책 생성
mc admin policy create $MYALIAS readlist new.json --insecure 

# 
mc admin policy list $MYALIAS --insecure
mc admin policy info $MYALIAS readlist --insecure | jq

기본정책 리스트

 

신규 정책 삽입된것을 확인 할 수 있다.

정책제거

#
mc admin policy remove $MYALIAS diagnostics --insecure
... # admin trace 에 출력 정보 확인
127.0.0.1:30002 [RESPONSE] [2025-09-21T15:02:56.901] [ Duration 1.213ms TTFB 1.211833ms ↑ 93 B  ↓ 303 B ]
127.0.0.1:30002 500 Internal Server Error
127.0.0.1:30002 {"Code":"InternalError","Message":"We encountered an internal error, please try again. (inbuilt policy `diagnostics` not allowed to be deleted)","Resource":"/minio/admin/v3/remove-canned-policy","RequestId":"186736EE5CE4E52F","HostId":"dd9025bab4ad464b049177c95eb6ebf374d3b3fd1af9251148b658df7ac2e3e8"}

#
mc admin policy remove $MYALIAS readlist --insecure

#
mc admin policy list $MYALIAS --insecure

 

신규 유저 생성

# user 없음 확인
mc admin user list $MYALIAS --insecure

# user1 생성 : 시크릿키 mypassword
mc admin user add $MYALIAS user1 mypassword --insecure
mc admin user list $MYALIAS --insecure
enabled    user1

 

user1 에 PBAC 정책 할당

#
mc admin policy list $MYALIAS --insecure
mc admin policy info $MYALIAS readwrite --insecure | jq
...
        "Effect": "Allow",
        "Action": [
          "s3:*"
        ],
        "Resource": [
          "arn:aws:s3:::*"
...

mc admin policy attach $MYALIAS readwrite --user user1 --insecure
Attached Policies: [readwrite]
To User: user1

#
mc admin policy entities $MYALIAS --policy readwrite --insecure
Query time: 2025-09-20T06:21:14Z
Policy -> Entity Mappings:
  Policy: readwrite
    User Mappings:
      user1

user1에 정책 적용

 

 

Group 에 user 할당 및 정책 부착

#
mc admin group list $MYALIAS --insecure

#
mc admin group add $MYALIAS devteam user1 --insecure
mc admin group info $MYALIAS devteam --insecure
Group: devteam
Status: enabled
Policy:
Members: user1

#
mc admin policy attach $MYALIAS readonly --group devteam --insecure
Attached Policies: [readonly]
To Group: devteam

#
mc admin group info $MYALIAS devteam --insecure
Group: devteam
Status: enabled
Policy: readonly
Members: user1

 

현재 Group 에 두 번째 정책 추가 후 동작 확인

# 참고로 3번째 줄 정책이 위 1,2 모두 포함됨
cat << EOF > s3-list.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:ListAllMyBuckets",
        "s3:ListBucket",
        "s3:List*" 
      ],
      "Resource": [
        "arn:aws:s3:::*"
      ]
    }
  ]
}
EOF

# lister 정책 생성
mc admin policy create $MYALIAS lister s3-list.json --insecure
mc admin policy list $MYALIAS --insecure

# devteam 그룹에 추가 부착
mc admin policy attach $MYALIAS lister --group devteam --insecure

# devteam 그룹에 2개의 정책 부착 확인
mc admin group info $MYALIAS devteam --insecure
Group: devteam
Status: enabled
Policy: lister,readonly
Members: user1

 

 

 

LDAP

 

배포할 Open LDAP 의 조직도를 LDIF로 만들예정

LDIF 구조는 다음과 같다.

# 전체 그림
## Users OU는 아직 사용자가 안 들어가 있지만 컨테이너만 생성
## Groups OU 안에 Admins/ Maintainers 그룹이 있고 사용자 DN이 멤버로 들어감
dc=minio,dc=io
├── cn=developer (사용자)
├── cn=maintainer (사용자)
├── cn=admin_root (사용자)
├── ou=Users (OU)
└── ou=Groups
    ├── cn=Admins (groupOfUniqueNames, member=admin_root)
    └── cn=Maintainers (groupOfUniqueNames, member=maintainer,developer)

    
# 사용자 엔트리들
dn: cn=developer,dc=minio,dc=io  # dn: 엔트리의 Distinguished Name(LDAP 경로)
changetype: add                  # changetype: add : 새 엔트리를 추가
objectclass: inetOrgPerson       # 속성(attribute)을 나열
cn: developer
givenname: developer
sn: Developer
displayname: Developer User
mail: developer@minio.io
userpassword: developer_pass

dn: cn=maintainer,dc=minio,dc=io
changetype: add
objectclass: inetOrgPerson       # LDAP에서 사람을 나타낼 때 쓰는 표준 객체 클래스
cn: maintainer                   # cn : Common Name (로그인 ID 같은 것)
givenname: maintainer
sn: Maintainer
displayname: Maintainer User     # displayname : LDAP UI 등에서 보여줄 이름
mail: maintainer@minio.io
userpassword: maintainer_pass    # userpassword : 사용자 비밀번호
...


# 조직 단위(OU) 생성
dn: ou=Groups,dc=minio,dc=io     # ou=Groups : 그룹들을 담는 컨테이너(Organizational Unit)
changetype: add
objectclass: organizationalUnit
ou: Groups

dn: ou=Users,dc=minio,dc=io      # ou=Users : 사용자를 담는 컨테이너
changetype: add
objectclass: organizationalUnit
ou: Users                        # 사용자들이 dc=minio,dc=io에 직접 만들어졌지만, 별도 OU로도 구분 가능


# 그룹 엔트리들
dn: cn=Admins,ou=Groups,dc=minio,dc=io
changetype: add
cn: Admins                       # cn=Admins 라는 그룹을 만들고
objectclass: groupOfUniqueNames  # objectclass: groupOfUniqueNames : 고유 멤버 DN을 갖는 그룹
uniqueMember: cn=admin_root,dc=minio,dc=io  # uniqueMember: 로 admin_root 사용자를 그룹 멤버로 추가

dn: cn=Maintainers,ou=Groups,dc=minio,dc=io
changetype: add
cn: Maintainers                  # Maintainers라는 그룹을 만들고
objectclass: groupOfUniqueNames
uniqueMember: cn=maintainer,dc=minio,dc=io # maintainer와 developer 두 명을 멤버로 지정
uniqueMember: cn=developer,dc=minio,dc=io

 

openldap 관리자 암호화 LDIF configmap 작성

#
cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
  name: openldap-admin-secret
type: Opaque
stringData:
  admin-password: "admin123"
EOF

#
cat << EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: openldap-bootstrap
  namespace: default
data:
  bootstrap.ldif: |
    dn: cn=developer,dc=minio,dc=io
    changetype: add
    objectclass: inetOrgPerson
    cn: developer
    givenname: developer
    sn: Developer
    displayname: Developer User
    mail: developer@minio.io
    userpassword: developer_pass

    dn: cn=maintainer,dc=minio,dc=io
    changetype: add
    objectclass: inetOrgPerson
    cn: maintainer
    givenname: maintainer
    sn: Maintainer
    displayname: Maintainer User
    mail: maintainer@minio.io
    userpassword: maintainer_pass

    dn: cn=admin_root,dc=minio,dc=io
    changetype: add
    objectclass: inetOrgPerson
    cn: admin_root
    givenname: admin_root
    sn: AdminRoot
    displayname: Admin User
    mail: admin_root@minio.io
    userpassword: admin_pass

    dn: ou=Groups,dc=minio,dc=io
    changetype: add
    objectclass: organizationalUnit
    ou: Groups

    dn: ou=Users,dc=minio,dc=io
    changetype: add
    objectclass: organizationalUnit
    ou: Users

    dn: cn=Admins,ou=Groups,dc=minio,dc=io
    changetype: add
    cn: Admins
    objectclass: groupOfUniqueNames
    uniqueMember: cn=admin_root,dc=minio,dc=io

    dn: cn=Maintainers,ou=Groups,dc=minio,dc=io
    changetype: add
    cn: Maintainers
    objectclass: groupOfUniqueNames
    uniqueMember: cn=maintainer,dc=minio,dc=io
    uniqueMember: cn=developer,dc=minio,dc=io
EOF

 

openldap 디플로이먼트/서비스 생성

cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: openldap
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: openldap
  template:
    metadata:
      labels:
        app: openldap
    spec:
      initContainers:
        - name: copy-bootstrap
          image: busybox
          command: ['sh', '-c', 'cp /config/bootstrap.ldif /ldif/bootstrap.ldif']
          volumeMounts:
            - name: configmap-ldif
              mountPath: /config
            - name: writable-ldif
              mountPath: /ldif
      containers:
        - name: openldap
          image: osixia/openldap:1.5.0
          env:
            - name: LDAP_ORGANISATION
              value: "MinIO"
            - name: LDAP_DOMAIN
              value: "minio.io"
            - name: LDAP_ADMIN_PASSWORD
              value: "admin123"
            - name: LDAP_REMOVE_CONFIG_AFTER_SETUP
              value: "false"
          ports:
            - containerPort: 389
            - containerPort: 636
          volumeMounts:
            - name: writable-ldif
              mountPath: /container/service/slapd/assets/config/bootstrap/ldif/custom
        - name: phpldapadmin
          image: osixia/phpldapadmin:0.9.0
          env:
            - name: PHPLDAPADMIN_LDAP_HOSTS
              value: "localhost"
            - name: PHPLDAPADMIN_HTTPS
              value: "false" # 기본 HTTP로 사용, HTTPS 쓰려면 true
          ports:
            - containerPort: 80
      volumes:
        - name: configmap-ldif
          configMap:
            name: openldap-bootstrap
        - name: writable-ldif
          emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: openldap
  namespace: default
spec:
  selector:
    app: openldap
  ports:
    - name: ldap
      port: 389
      targetPort: 389
    - name: ldaps
      port: 636
      targetPort: 636
    - name: http
      port: 80
      targetPort: 80
EOF

 

배포 확인

#
kubectl get deploy,pod,svc,ep,cm,secret

# 컨테이너가 시작되면 /container/service/slapd/assets/config/bootstrap/ldif/custom/bootstrap.ldif 를 읽어서 자동으로 ldapadd 적용.
# 결과적으로 LDAP 서버가 처음 뜰 때 바로 사용자를 생성해 둡니다. 이후 kubectl exec로 들어가서 ldapsearch로 확인할 수 있습니다:

# 관리자 DN(암호)로 ldapsearch 확인
kubectl exec -it deploy/openldap -c openldap -- \
  ldapsearch -x -H ldap://localhost -D "cn=admin,dc=minio,dc=io" -w admin123 -b "dc=minio,dc=io"

 

AD/LDAP 액세스 관리

  • MinIO는 외부 ID 관리를 위해 단일 AD 또는 LDAP 서비스를 지원하며, 이를 활성화하면 내부 IDP는 비활성화됩니다.
  • 외부 AD/LDAP에서 관리되는 사용자의 고유 이름(DN)을 기반으로 MinIO 내 기존 정책에 매핑하고, 그룹 멤버십 역시 정책 매핑에 활용됩니다.
  • 인증 시 MinIO는 AD/LDAP 자격증명을 확인하고, DN 및 그룹 DN과 매칭되는 정책을 찾아 인증된 사용자에게 할당하며, 이를 기반으로 임시 자격증명(STS)을 생성합니다.
  • 명시된 정책이 없으면 모든 리소스 및 작업에 대한 접근이 거부되며, AD/LDAP 사용자는 정책에 연결된 액세스 키도 생성할 수 있습니다.

사용자 DN에 대한 정책 매핑

mc idp ldap policy attach myminio consoleAdmin \
  --user='cn=sisko,cn=users,dc=example,dc=com'

mc idp ldap policy attach myminio readwrite,diagnostics \
  --user='cn=dax,cn=users,dc=example,dc=com'
  • **cn=sisko,cn=users,dc=example,dc=com**MinIO는 정책 과 일치하는 DN을 가진 인증된 사용자를 할당하여 consoleAdmin MinIO 서버에 대한 완전한 액세스 권한을 부여합니다.
  • MinIO는 및 정책 cn=dax,cn=users,dc=example,dc=com모두와 일치하는 DN을 가진 인증된 사용자를 할당하여 MinIO 서버에 대한 일반적인 읽기/쓰기 액세스 권한 과 진단 관리 작업에 대한 액세스 권한을 부여합니다.
  • MinIO는 DN이 일치하는 인증된 사용자에게 정책을 할당하지 않으며 **cn=quark,cn=users,dc=example,dc=com**API 작업에 대한 모든 액세스를 거부합니다.

그룹 DN에 대한 정책 매핑

mc idp ldap policy attach myminio consoleAdmin \
  --group='cn=ops,cn=groups,dc=example,dc=com'

mc idp ldap policy attach myminio diagnostics \
  --group='cn=engineering,cn=groups,dc=example,dc=com'
  • **cn=ops,cn=groups,dc=example,dc=com**MinIO는 AD/LDAP 그룹 의 멤버십을 가진 모든 인증 사용자에게 consoleAdmin정책을 할당하여 MinIO 서버에 대한 완전한 액세스 권한을 부여합니다.
  • **cn=engineering,cn=groups,dc=example,dc=com**MinIO는 AD/LDAP 그룹 의 멤버십을 가진 모든 인증 사용자에게 diagnostics정책을 할당하여 진단 관리 작업에 대한 액세스 권한을 부여합니다.

 

 

MinIO 에 LDAP 설정

dc=minio,dc=io
├── cn=developer (사용자)
├── cn=maintainer (사용자)
├── cn=admin_root (사용자)
├── ou=Users (OU)
└── ou=Groups
    ├── cn=Admins (groupOfUniqueNames, member=admin_root)
    └── cn=Maintainers (groupOfUniqueNames, member=maintainer,developer)
    
    ##위 정보의 LDAP을 등록할 예정

 

  • Server Insecure : Enabled
  • Server Address : openldap.default.svc.cluster.local:389
  • Lookup Bind DN* : cn=admin,dc=**minio**,dc=io
  • Lookup Bind Password* : admin123
  • User DN Search Base* : **dc**=minio,dc=io
  • User DN Search Filter* : (cn=%s)

 

 

 

그룹 맵핑 검색 설정

#
mc idp ldap update $MYALIAS \
  group_search_filter="(&(objectClass=groupOfUniqueNames)(uniqueMember=%d))" \
  group_search_base_dn="dc=minio,dc=io" --insecure

mc admin service restart $MYALIAS --insecure

#
mc idp ldap info $MYALIAS --insecure
╭─────────────────────────────────────────────────────────────────────────────╮
│                enable: on                                                   │
│  group_search_base_dn: dc=minio,dc=io                                       │
│   group_search_filter: (&(objectClass=groupOfUniqueNames)(uniqueMember=%d)) │
│        lookup_bind_dn: cn=admin,dc=minio,dc=io                              │
│           server_addr: openldap.default.svc.cluster.local:389               │
│       server_insecure: on                                                   │
│user_dn_search_base_dn: dc=minio,dc=io                                       │
│ user_dn_search_filter: (cn=%s)                                              │
╰─────────────────────────────────────────────────────────────────────────────╯

 

 

그룹맵핑 정책 설정

# 전체 그림
dc=minio,dc=io
├── cn=developer (사용자)
├── cn=maintainer (사용자)
├── cn=admin_root (사용자)
├── ou=Users (OU)
└── ou=Groups
    ├── cn=Admins (groupOfUniqueNames, member=admin_root)
    └── cn=Maintainers (groupOfUniqueNames, member=maintainer,developer)

#
mc idp ldap policy attach $MYALIAS readwrite \
  --group="cn=Maintainers,ou=Groups,dc=minio,dc=io" --insecure
Attached Policies: [readwrite]
To Group: cn=Maintainers,ou=Groups,dc=minio,dc=io

#
mc admin user list $MYALIAS --insecure
enabled    cn=admin_root,dc=...  consoleAdmin

#
mc admin group list $MYALIAS --insecure
cn=Maintainers,ou=Groups,dc=minio,dc=io
devteam

    dn: cn=developer,dc=minio,dc=io
    changetype: add
    objectclass: inetOrgPerson
    cn: developer
    givenname: developer
    sn: Developer
    displayname: Developer User
    mail: developer@minio.io
    userpassword: developer_pass

 

 

SDK(실습에선 Python으로 진행)

기본 설정

#
docker exec -it myk8s-control-plane bash
-----------------------------------------
#
python3 -V

#
apt update && apt install -y python3-pip nano git tree

#
pip3 list 
pip3 install minio --break-system-packages
pip3 show minio

#
git clone https://github.com/minio/minio-py
cd minio-py/examples
tree 
|-- append_object.py
|-- bucket_exists.py
...

#
cat list_buckets.py
from minio import Minio

client = Minio(
    "play.min.io",
    access_key="Q3AM3UQ867SPQQA43P2F",
    secret_key="zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG",
)

buckets = client.list_buckets()
for bucket in buckets:
    print(bucket.name, bucket.creation_date)

#
python3 list_buckets.py
...
-----------------------------------------

minio 제공되는 기본 버킷을 확인할 수 있다.

 

devuser 생성

# user 확인
MYALIAS=k8s-tenant1
mc admin user list $MYALIAS --insecure

# devuser 생성 : 시크릿키 devpassword
mc admin user add $MYALIAS devuser devpassword --insecure
mc admin user list $MYALIAS --insecure

# devuser 에 readwrite 정책 부착
mc admin policy attach $MYALIAS readwrite --user devuser --insecure
mc admin policy entities $MYALIAS --policy readwrite --insecure

 

 

Python SDK를 활용하여 버킷 목록 조회

# 아래는 myk8s-controller-plane 컨테이너 내부에서 실행
kubectl get secret -n tenant1 tenant1-tls -o jsonpath='{.data.public\.crt}' | base64 -d > tenant1.crt
kubectl get secret -n tenant1 tenant1-tls -o jsonpath='{.data.public\.crt}' | base64 -d | openssl x509 -noout -text
cp tenant1.crt /usr/local/share/ca-certificates/tenant1.crt
update-ca-certificates

#
echo "127.0.0.1 minio.tenant1.svc.cluster.local" >> /etc/hosts

#
cat << EOF > my_list_bucket.py
from minio import Minio

client = Minio(
    "minio.tenant1.svc.cluster.local:30002",
    access_key="devuser",
    secret_key="devpassword",
    secure=True,
    cert_check=False
)

buckets = client.list_buckets()
for bucket in buckets:
    print(bucket.name, bucket.creation_date)
EOF
cat my_list_bucket.py

# 버킷 목록 조회
python3 my_list_bucket.py
mybucket 2025-09-21 04:24:16.252000+00:00

# mc admin trace $MYALIAS --verbose --all --insecure
minio.tenant1.svc.cluster.local:30002 [REQUEST s3.ListBuckets] [2025-09-21T17:36:30.032] [Client IP: 10.244.0.1]
minio.tenant1.svc.cluster.local:30002 GET /
minio.tenant1.svc.cluster.local:30002 Proto: HTTP/1.1
minio.tenant1.svc.cluster.local:30002 Host: minio.tenant1.svc.cluster.local:30002
minio.tenant1.svc.cluster.local:30002 Authorization: AWS4-HMAC-SHA256 Credential=devuser/20250921/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=b73e560231834d88820360e3304ee9fa7ec9f9f26b75de8ed6aef389458fc86d

 

Python SDK를 활용하여 객체 업로드

# 바로 위 sample.json 에서 파일 작성 후 json 파일 확인
cat sample.json | jq

#
cat << EOF > labf.py
from urllib import request

def rprint(result):
	print(
		"Object name: {0}; last modified: {1}".format(
			result.object_name, result.last_modified
		)
	)

def opensky_stream():
	return request.urlopen(
		"https://opensky-network.org/api/states/all",
	)

def obj_head(response):
	print("Object head: {0}".format(response.read(100)))

def eprint(errors):
	for error in errors:
		print("Error occurred while deleting object", error)
EOF

#
cat << EOF > my_put_object.py
from minio import Minio
from minio.credentials import LdapIdentityProvider
from minio.commonconfig import CopySource
from minio.deleteobjects import DeleteObject
from time import sleep
import labf

# Create a client.
client = Minio(
    "minio.tenant1.svc.cluster.local:30002",
    access_key="devuser",
    secret_key="devpassword",
    secure=True,
    cert_check=False
)

result = client.fput_object("mybucket", "sample.json", "sample.json", "application/json")

labf.rprint(result)
EOF

#
python3 my_put_object.py
Object name: sample.json; last modified: None

#
mc ls $MYALIAS --recursive --insecure
[2025-09-21 17:53:11 KST] 6.1KiB STANDARD mybucket/sample.json

 

 

실습 환경을 클라우드 포메이션으로 배포

 

이 스크립트는 CloudFormation 템플릿을 이용해 EC2 기반의 MinIO 환경을 자동 배포하고 관리하는 예제입니다.
스택 생성 시 SSH 키와 접속 허용 IP, 인스턴스 타입 등을 파라미터로 지정할 수 있습니다.
배포 후에는 EC2의 퍼블릭 IP를 확인해 SSH 접속 및 Kubernetes(k3s) 환경 구성을 확인할 수 있습니다.

# YAML 파일 다운로드
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/minio-ec2-1node.yaml

# CloudFormation 스택 배포
# aws cloudformation deploy --template-file kans-7w.yaml --stack-name mylab --parameter-overrides KeyName=<My SSH Keyname> SgIngressSshCidr=<My Home Public IP Address>/32 --region ap-northeast-2
예시) aws cloudformation deploy --template-file minio-ec2-1node.yaml --stack-name miniolab --parameter-overrides KeyName=kp-gasida SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 --region ap-northeast-2

## Tip. 인스턴스 타입 변경 : MyInstanceType=t3.xlarge (vCPU 4, Mem 16)
예시) aws cloudformation deploy --template-file minio-ec2-1node.yaml --stack-name miniolab --parameter-overrides MyInstanceType=t3.xlarge KeyName=kp-gasida SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 --region ap-northeast-2

# CloudFormation 스택 배포 완료 후 작업용 EC2 IP 출력
aws cloudformation describe-stacks --stack-name miniolab --query 'Stacks[*].Outputs[0].OutputValue' --output text --region ap-northeast-2

# [모니터링] CloudFormation 스택 상태 : 생성 완료 확인
while true; do 
  date
  AWS_PAGER="" aws cloudformation list-stacks \
    --stack-status-filter CREATE_IN_PROGRESS CREATE_COMPLETE CREATE_FAILED DELETE_IN_PROGRESS DELETE_FAILED \
    --query "StackSummaries[*].{StackName:StackName, StackStatus:StackStatus}" \
    --output table
  sleep 1
done

# 배포된 aws ec2 유동 공인 IP 확인
aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output text
k3s-s   43.202.60.44    running

# EC2 SSH 접속 : 바로 접속하지 말고, 3~5분 정도 후에 접속 할 것
ssh -i ~/.ssh/kp-gasida.pem ubuntu@$(aws cloudformation describe-stacks --stack-name miniolab --query 'Stacks[*].Outputs[0].OutputValue' --output text --region ap-northeast-2)
...
(⎈|default:N/A) root@k3s-s:~# <- kubeps 가 나오지 않을 경우 ssh logout 후 다시 ssh 접속 할 것!

 

 

kc get node -owide
NAME     STATUS   ROLES                  AGE     VERSION         INTERNAL-IP      EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION   CONTAINER-RUNTIME
k3s-s    Ready    control-plane,master   2m12s   v1.28.15+k3s1   192.168.10.10    <none>        Ubuntu 22.04.5 LTS   6.8.0-1029-aws   containerd://1.7.22-k3s1.28

hostnamectl 
...
 Hardware Vendor: Amazon EC2
  Hardware Model: t3.xlarge

# Install Krew
wget -P /root "https://github.com/kubernetes-sigs/krew/releases/latest/download/krew-linux_amd64.tar.gz"
tar zxvf "/root/krew-linux_amd64.tar.gz" --warning=no-unknown-keyword
./krew-linux_amd64 install krew
export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH" # export PATH="$PATH:/root/.krew/bin"
echo 'export PATH="$PATH:/root/.krew/bin:/root/go/bin"' >> /etc/profile
kubectl krew install get-all neat rolesum pexec stern
kubectl krew list

이 스크립트는 Kubernetes 플러그인 관리 도구인 Krew를 EC2(k3s 클러스터) 환경에 설치하는 과정입니다.
설치 후 PATH를 설정하여 krew와 플러그인들을 전역에서 사용할 수 있게 합니다.
마지막으로 get-all, neat, rolesum, pexec, stern 등의 유용한 kubectl 플러그인들을 설치하고 목록을 확인합니다.

 

 

#
lsblk -a -o NAME,KNAME,MAJ:MIN,SIZE,TYPE,MOUNTPOINT,FSTYPE,UUID,MODEL,SERIAL
NAME         KNAME      MAJ:MIN  SIZE TYPE MOUNTPOINT                   FSTYPE   UUID                                 MODEL                      SERIAL
...
nvme0n1      nvme0n1    259:0     30G disk                                                                            Amazon Elastic Block Store vol0833a7ec553e64b74
├─nvme0n1p1  nvme0n1p1  259:5     29G part /                            ext4     0eec2352-4b50-40ec-ae93-7ce2911392bb                            
├─nvme0n1p14 nvme0n1p14 259:6      4M part                                                                                                       
├─nvme0n1p15 nvme0n1p15 259:7    106M part /boot/efi                    vfat     2586-E57C                                                       
└─nvme0n1p16 nvme0n1p16 259:8    913M part /boot                        ext4     1eb1aa76-4a46-48d9-95d8-a2ecf2d505c2                            
nvme1n1      nvme1n1    259:1     30G disk                                                                            Amazon Elastic Block Store vol0da5a82ea4ada0efc
nvme3n1      nvme3n1    259:2     30G disk                                                                            Amazon Elastic Block Store vol079fd88ba55971938
nvme2n1      nvme2n1    259:3     30G disk                                                                            Amazon Elastic Block Store vol012134019ba9f1c8a
nvme4n1      nvme4n1    259:4     30G disk                                                                            Amazon Elastic Block Store vol04d49a7ef31a119a2

# fio 설치 되어 있음
apt install fio -y
fio -h

# 측정 기본
## IOPS: 초당 입출력 횟수
## Throughput: 초당 MB 전송량
## Block size, Queue depth, RW 패턴 (랜덤/순차, 읽기/쓰기) 조합에 따라 결과가 달라집니다.
## AWS gp3는 기본 IOPS 3000/Throughput 125MB/s

# 4k 랜덤 읽기/쓰기, iodepth=16, numjobs=4
## --rw=randrw : 랜덤 읽기/쓰기 혼합
## --rwmixread=70 : 70% 읽기 / 30% 쓰기
## --bs=4k : 4KB 블록 → IOPS 측정용
## --iodepth=16 : 큐 깊이 16
## --numjobs=4 : 4개의 병렬 job
## --time_based --runtime=60 : 60초 동안 측정
## --group_reporting : 그룹 단위 결과 요약
fio --name=randrw_test \
  --filename=/mnt/testfile \
  --size=4G \
  --rw=randrw \
  --rwmixread=70 \
  --bs=4k \
  --iodepth=16 \
  --numjobs=4 \
  --time_based \
  --runtime=60 \
  --group_reporting

## 읽기 평균 IOPS : 2388
  read: IOPS=2388, BW=9553KiB/s (9782kB/s)(560MiB/60003msec)

## 쓰기 평균 IOPS : 1030
  write: IOPS=1030, BW=4121KiB/s (4220kB/s)(241MiB/60003msec); 0 zone resets

## 디스크 활용률 : 읽기/쓰기 IO 수량(132k/49k) , 디스크 활용률 87%
Disk stats (read/write):
  nvme0n1: ios=132972/49709, sectors=1063776/420736, merge=0/8, ticks=232695/154017, in_queue=386713, util=87.09%

이 스크립트는 EC2에 연결된 EBS 디스크들의 상태를 확인한 뒤, fio를 이용해 성능을 벤치마크하는 과정입니다.
4KB 블록 단위로 랜덤 읽기·쓰기 테스트를 수행하며, iodepth=16, 병렬 4 job으로 60초간 측정합니다.
결과로 읽기/쓰기 IOPS와 전송량, 디스크 활용률을 확인하여 EBS 성능을 평가할 수 있습니다.

 

 

# mlocate or plocate
systemctl disable --now plocate-updatedb 
systemctl list-timers | grep locate

# updatedb
systemctl disable --now updatedb.timer
systemctl list-timers | grep updatedb

# auditd
systemctl disable --now auditd
systemctl list-timers | grep audit

# Crowdstrike Falcon & Antivirus software (clamav)

MinIO 최적화 튜닝

- 파일 시스템, 시스템 수준 호출 또는 커널 수준 호출을 인덱싱, 스캔 또는 감사하는 시스템 서비스를 비활성화합니다.
- 이러한 서비스는 리소스 경합이나 MinIO 작업 차단으로 인해 성능이 저하될 수 있습니다.
- MinIO는 MinIO를 실행하는 호스트에서 다음 서비스를 제거하거나 비활성화할 것을 강력히 권장합니다.

 

#
sysctl -a > before.txt


# 설치 되어 있음
apt install tuned -y

# 서비스 시작
systemctl start tuned && systemctl enable tuned
cat /usr/lib/systemd/system/tuned.service

# To see the current active profile, run:
tuned-adm active
Current active profile: throughput-performance

# To show the current profile selection mode, run:
tuned-adm profile_mode
Profile selection mode: auto

# To list all available profiles, run:
tuned-adm list
Available profiles:
- accelerator-performance     - Throughput performance based tuning with disabled higher latency STOP states
- aws                         - Optimize for aws ec2 instances
- balanced                    - General non-specialized tuned profile
- balanced-battery            - Balanced profile biased towards power savings changes for battery
- desktop                     - Optimize for the desktop use-case
- hpc-compute                 - Optimize for HPC compute workloads
- intel-sst                   - Configure for Intel Speed Select Base Frequency
- latency-performance         - Optimize for deterministic performance at the cost of increased power consumption
- network-latency             - Optimize for deterministic performance at the cost of increased power consumption, focused on low latency network performance
- network-throughput          - Optimize for streaming network throughput, generally only necessary on older CPUs or 40G+ networks
- optimize-serial-console     - Optimize for serial console use.
- powersave                   - Optimize for low power consumption
- throughput-performance      - Broadly applicable tuning that provides excellent performance across a variety of common server workloads
- virtual-guest               - Optimize for running inside a virtual guest
- virtual-host                - Optimize for running KVM guests
Current active profile: throughput-performance

# To switch to a different profile, run:
## The enabled profile is persisted into /etc/tuned/active_profile, which is read when the daemon starts or is restarted.
tuned-adm profile <profile-name>

# To disable all tunings, run:
tuned-adm off

tuned-adm active
No current active profile.

#
tuned-adm profile virtual-guest

tuned-adm active
Current active profile: virtual-guest

#
sysctl -a > after.txt

#
vi -d before.txt after.txt


# 4k 랜덤 읽기/쓰기, iodepth=16, numjobs=4
fio --name=randrw_test \
  --filename=/mnt/testfile \
  --size=4G \
  --rw=randrw \
  --rwmixread=70 \
  --bs=4k \
  --iodepth=16 \
  --numjobs=4 \
  --time_based \
  --runtime=60 \
  --group_reporting

## 읽기 평균 IOPS : 2388
  read: IOPS=2395, BW=9584KiB/s (9814kB/s)(562MiB/60003msec)

## 읽기 평균 IOPS : 1030
  write: IOPS=1033, BW=4134KiB/s (4233kB/s)(242MiB/60003msec); 0 zone resets

## 디스크 활용률 : 읽기/쓰기 IO 수량(132k/49k) , 디스크 활용률 87%
Disk stats (read/write):
  nvme0n1: ios=133343/49277, sectors=1066744/416808, merge=0/43, ticks=231976/154776, in_queue=386752, util=86.35%

tuned profiles

 

 

DirectPV

DirectPV란 ?

MinIO DirectPV는 AGPL 3.0 라이선스 하에 배포되는 Kubernetes용 Container Storage Interface(CSI) 드라이버로, 직접 연결된 스토리지(Direct Attached Storage)를 위한 분산 퍼시스턴트 볼륨 관리자입니다.

  • DirectPV는 SAN(스토리지 영역 네트워크)이나 NAS(네트워크 연결 스토리지)와 같은 스토리지 시스템 자체가 아니라, 여러 서버에 걸친 로컬 드라이브를 발견(discover), 포맷(format), 마운트(mount), 스케줄(schedule), 모니터링(monitor)하는 기능을 제공합니다.
  • Kubernetes에서 기존 hostPath나 local PV가 정적이고 제한적인 반면, DirectPV는 경량이며 수만 개의 드라이브를 확장 가능하게 관리할 수 있도록 설계되었습니다.

구성요소

  • 명령줄 인터페이스를 통해 DirectPV CSI 드라이버를 관리하기 위해 로컬 머신에 설치된 DirectPV 플러그인
  • 로컬 볼륨을 프로비저닝하는 Kubernetes 클러스터에 직접 설치된 DirectPV CSI 드라이버

 

DirectPV 설치

#
k krew install directpv
k directpv -h

# 
k directpv install
┌──────────────────────────────────────┬──────────────────────────┐
│ NAME                                 │ KIND                     │
├──────────────────────────────────────┼──────────────────────────┤
│ directpv                             │ Namespace                │
│ directpv-min-io                      │ ServiceAccount           │
│ directpv-min-io                      │ ClusterRole              │
│ directpv-min-io                      │ ClusterRoleBinding       │
│ directpv-min-io                      │ Role                     │
│ directpv-min-io                      │ RoleBinding              │
│ directpvdrives.directpv.min.io       │ CustomResourceDefinition │
│ directpvvolumes.directpv.min.io      │ CustomResourceDefinition │
│ directpvnodes.directpv.min.io        │ CustomResourceDefinition │
│ directpvinitrequests.directpv.min.io │ CustomResourceDefinition │
│ directpv-min-io                      │ CSIDriver                │
│ directpv-min-io                      │ StorageClass             │
│ node-server                          │ Daemonset                │
│ controller                           │ Deployment               │
└──────────────────────────────────────┴──────────────────────────┘

#
k get crd | grep min
directpvdrives.directpv.min.io         2025-09-13T05:23:40Z
directpvinitrequests.directpv.min.io   2025-09-13T05:23:40Z
directpvnodes.directpv.min.io          2025-09-13T05:23:40Z
directpvvolumes.directpv.min.io        2025-09-13T05:23:40Z

k get sc directpv-min-io -o yaml | yq
k get sc
NAME                   PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
directpv-min-io        directpv-min-io         Delete          WaitForFirstConsumer   true                   4m17s

k get-all -n directpv
k get deploy,ds,pod -n directpv
k rolesum directpv-min-io -n directpv

k get directpvnodes.directpv.min.io
k get directpvnodes.directpv.min.io -o yaml | yq


# controller 파드 1대 정보 확인
kc describe pod -n directpv controller-75d5595d5b-bqwbs
...
Containers:
  csi-provisioner:
    ...
    Args:
      --v=3
      --timeout=300s
      --csi-address=$(CSI_ENDPOINT)
      --leader-election
      --feature-gates=Topology=true
      --strict-topology
    ...
    Environment:
      CSI_ENDPOINT:  unix:///csi/csi.sock
    Mounts:
      /csi from socket-dir (rw)
  ...
  csi-resizer:
    ...
    Args:
      --v=3
      --timeout=300s
      --csi-address=$(CSI_ENDPOINT)
      --leader-election
    ...
    Environment:
      CSI_ENDPOINT:  unix:///csi/csi.sock
    Mounts:
      /csi from socket-dir (rw)
  ...
  controller:
    ...
    Args:
      controller
      --identity=directpv-min-io
      -v=3
      --csi-endpoint=$(CSI_ENDPOINT)
      --kube-node-name=$(KUBE_NODE_NAME)
      --readiness-port=30443
    ...
    Environment:
      KUBE_NODE_NAME:   (v1:spec.nodeName)
      CSI_ENDPOINT:    unix:///csi/csi.sock
    Mounts:
      /csi from socket-dir (rw)
...
Volumes:
  socket-dir:
    Type:          HostPath (bare host directory volume)
    Path:          /var/lib/kubelet/plugins/controller-controller
    HostPathType:  DirectoryOrCreate
...

ls -l /var/lib/kubelet/plugins/controller-controller
total 0
srwxr-xr-x 1 root root 0 Sep 14 11:17 csi.sock

k get lease -n directpv -o yaml | yq
k get lease -n directpv
...

#
k get pod -n directpv -l selector.directpv.min.io.service=enabled
kc describe pod -n directpv -l selector.directpv.min.io.service=enabled
...
Containers:
  node-driver-registrar:
    ...
    Args:
      --v=3
      --csi-address=unix:///csi/csi.sock
      --kubelet-registration-path=/var/lib/kubelet/plugins/directpv-min-io/csi.sock

  node-server:
    ...
    Args:
      node-server
      -v=3
      --identity=directpv-min-io
      --csi-endpoint=$(CSI_ENDPOINT)
      --kube-node-name=$(KUBE_NODE_NAME)
      --readiness-port=30443
      --metrics-port=10443

  node-controller:
    ...
    Args:
      node-controller
      -v=3
      --kube-node-name=$(KUBE_NODE_NAME)

  liveness-probe:
    ...
    Args:
      --csi-address=/csi/csi.sock
      --health-port=9898

Volumes:
  socket-dir:
    Type:          HostPath (bare host directory volume)
    Path:          /var/lib/kubelet/plugins/directpv-min-io
    HostPathType:  DirectoryOrCreate
  mountpoint-dir:
    Type:          HostPath (bare host directory volume)
    Path:          /var/lib/kubelet/pods
    HostPathType:  DirectoryOrCreate
  registration-dir:
    Type:          HostPath (bare host directory volume)
    Path:          /var/lib/kubelet/plugins_registry
    HostPathType:  DirectoryOrCreate
  plugins-dir:
    Type:          HostPath (bare host directory volume)
    Path:          /var/lib/kubelet/plugins
    HostPathType:  DirectoryOrCreate
  directpv-common-root:
    Type:          HostPath (bare host directory volume)
    Path:          /var/lib/directpv/
    HostPathType:  DirectoryOrCreate
  direct-csi-common-root:
    Type:          HostPath (bare host directory volume)
    Path:          /var/lib/direct-csi/
    HostPathType:  DirectoryOrCreate
  sysfs:
    Type:          HostPath (bare host directory volume)
    Path:          /sys
    HostPathType:  DirectoryOrCreate
  devfs:
    Type:          HostPath (bare host directory volume)
    Path:          /dev
    HostPathType:  DirectoryOrCreate
  run-udev-data-dir:
    Type:          HostPath (bare host directory volume)
    Path:          /run/udev/data
    HostPathType:  DirectoryOrCreate
...

 

DirectPV로 물리디스크 관리하기

#
k directpv info
┌─────────┬──────────┬───────────┬─────────┬────────┐
│ NODE    │ CAPACITY │ ALLOCATED │ VOLUMES │ DRIVES │
├─────────┼──────────┼───────────┼─────────┼────────┤
│ • k3s-s │ -        │ -         │ -       │ -      │
└─────────┴──────────┴───────────┴─────────┴────────┘

#
k directpv discover
 Discovered node 'k3s-s' ✔
┌─────────────────────┬───────┬─────────┬────────┬────────────┬────────────────────────────┬───────────┬─────────────┐
│ ID                  │ NODE  │ DRIVE   │ SIZE   │ FILESYSTEM │ MAKE                       │ AVAILABLE │ DESCRIPTION │
├─────────────────────┼───────┼─────────┼────────┼────────────┼────────────────────────────┼───────────┼─────────────┤
│ 259:1$Pna+Q57t+y... │ k3s-s │ nvme1n1 │ 30 GiB │ -          │ Amazon Elastic Block Store │ YES       │ -           │
│ 259:3$QURUJfcllF... │ k3s-s │ nvme2n1 │ 30 GiB │ -          │ Amazon Elastic Block Store │ YES       │ -           │
│ 259:2$/28TF0pThR... │ k3s-s │ nvme3n1 │ 30 GiB │ -          │ Amazon Elastic Block Store │ YES       │ -           │
│ 259:4$+AaiQHygVH... │ k3s-s │ nvme4n1 │ 30 GiB │ -          │ Amazon Elastic Block Store │ YES       │ -           │
└─────────────────────┴───────┴─────────┴────────┴────────────┴────────────────────────────┴───────────┴─────────────┘
Generated 'drives.yaml' successfully.

# (참고) 적용 예외 설정 시 select: "no" 설정
cat drives.yaml | yq
{
  "version": "v1",
  "nodes": [
    {
      "name": "k3s-s",
      "drives": [
        {
          "id": "259:4$+AaiQHygVHSl7AmoNWxVgP1kJsoha2CeGIzxfuoqBvw=",
          "name": "nvme4n1",
          "size": 32212254720,
          "make": "Amazon Elastic Block Store",
          "select": "yes"
        },
...

#
k directpv init drives.yaml
ERROR Initializing the drives will permanently erase existing data. Please review carefully before performing this *DANGEROUS* operation and retry this command with --dangerous flag.

# Perform initialization of drives which will permanently erase existing data
k directpv init drives.yaml --dangerous
Processed initialization request '75caf826-e374-4ff2-b4d6-bdbf1ac0e847' for node 'k3s-s' ✔

┌──────────────────────────────────────┬───────┬─────────┬─────────┐
│ REQUEST_ID                           │ NODE  │ DRIVE   │ MESSAGE │
├──────────────────────────────────────┼───────┼─────────┼─────────┤
│ 75caf826-e374-4ff2-b4d6-bdbf1ac0e847 │ k3s-s │ nvme1n1 │ Success │
│ 75caf826-e374-4ff2-b4d6-bdbf1ac0e847 │ k3s-s │ nvme2n1 │ Success │
│ 75caf826-e374-4ff2-b4d6-bdbf1ac0e847 │ k3s-s │ nvme3n1 │ Success │
│ 75caf826-e374-4ff2-b4d6-bdbf1ac0e847 │ k3s-s │ nvme4n1 │ Success │
└──────────────────────────────────────┴───────┴─────────┴─────────┘

#
k directpv list drives
┌───────┬─────────┬────────────────────────────┬────────┬────────┬─────────┬────────┐
│ NODE  │ NAME    │ MAKE                       │ SIZE   │ FREE   │ VOLUMES │ STATUS │
├───────┼─────────┼────────────────────────────┼────────┼────────┼─────────┼────────┤
│ k3s-s │ nvme1n1 │ Amazon Elastic Block Store │ 30 GiB │ 30 GiB │ -       │ Ready  │
│ k3s-s │ nvme2n1 │ Amazon Elastic Block Store │ 30 GiB │ 30 GiB │ -       │ Ready  │
│ k3s-s │ nvme3n1 │ Amazon Elastic Block Store │ 30 GiB │ 30 GiB │ -       │ Ready  │
│ k3s-s │ nvme4n1 │ Amazon Elastic Block Store │ 30 GiB │ 30 GiB │ -       │ Ready  │
└───────┴─────────┴────────────────────────────┴────────┴────────┴─────────┴────────┘

k directpv info
┌─────────┬──────────┬───────────┬─────────┬────────┐
│ NODE    │ CAPACITY │ ALLOCATED │ VOLUMES │ DRIVES │
├─────────┼──────────┼───────────┼─────────┼────────┤
│ • k3s-s │ 120 GiB  │ 0 B       │ 0       │ 4      │
└─────────┴──────────┴───────────┴─────────┴────────┘
0 B/120 GiB used, 0 volumes, 4 drives


lsblk
NAME         MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
...
nvme1n1      259:1    0   30G  0 disk /var/lib/directpv/mnt/03ab4af0-14e3-47d2-ae39-2742c986a71d
nvme3n1      259:2    0   30G  0 disk /var/lib/directpv/mnt/8d0c7b6f-07f8-4908-bca6-5c14ad068400
nvme2n1      259:3    0   30G  0 disk /var/lib/directpv/mnt/324af346-29f1-4a9b-93bf-fbae1f21302d
nvme4n1      259:4    0   30G  0 disk /var/lib/directpv/mnt/088e2e11-8708-4a6c-8466-631c89c03f5c

df -hT --type xfs
/dev/nvme1n1    xfs        30G  248M   30G   1% /var/lib/directpv/mnt/03ab4af0-14e3-47d2-ae39-2742c986a71d
/dev/nvme4n1    xfs        30G  248M   30G   1% /var/lib/directpv/mnt/088e2e11-8708-4a6c-8466-631c89c03f5c
/dev/nvme3n1    xfs        30G  248M   30G   1% /var/lib/directpv/mnt/8d0c7b6f-07f8-4908-bca6-5c14ad068400
/dev/nvme2n1    xfs        30G  248M   30G   1% /var/lib/directpv/mnt/324af346-29f1-4a9b-93bf-fbae1f21302d

tree -h /var/lib/directpv/
[4.0K]  /var/lib/directpv/
├── [4.0K]  mnt
│   ├── [  75]  03ab4af0-14e3-47d2-ae39-2742c986a71d
│   ├── [  75]  088e2e11-8708-4a6c-8466-631c89c03f5c
│   ├── [  75]  324af346-29f1-4a9b-93bf-fbae1f21302d
│   └── [  75]  8d0c7b6f-07f8-4908-bca6-5c14ad068400
└── [  40]  tmp

#
k get directpvdrives.directpv.min.io -o yaml | yq
k get directpvdrives.directpv.min.io
NAME                                   AGE
03ab4af0-14e3-47d2-ae39-2742c986a71d   5m58s
088e2e11-8708-4a6c-8466-631c89c03f5c   5m58s
324af346-29f1-4a9b-93bf-fbae1f21302d   5m58s
8d0c7b6f-07f8-4908-bca6-5c14ad068400   5m58s

#
cat /etc/fstab

 

#
df -hT --type xfs
Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/nvme4n1   xfs    30G  248M   30G   1% /var/lib/directpv/mnt/ab82b195-5204-47fa-a864-a351f66b38d2
/dev/nvme2n1   xfs    30G  248M   30G   1% /var/lib/directpv/mnt/94be15e6-5d0d-49a9-a6d8-d139977a3446
/dev/nvme1n1   xfs    30G  248M   30G   1% /var/lib/directpv/mnt/dc26b422-5364-464e-bc54-a6ab6b8d4e32
/dev/nvme3n1   xfs    30G  248M   30G   1% /var/lib/directpv/mnt/246f427a-3e32-4311-b81c-fc9e45ebc6ad

lsblk
...
nvme1n1      259:0    0   30G  0 disk /var/lib/directpv/mnt/dc26b422-5364-464e-bc54-a6ab6b8d4e32
nvme3n1      259:2    0   30G  0 disk /var/lib/directpv/mnt/246f427a-3e32-4311-b81c-fc9e45ebc6ad
nvme4n1      259:3    0   30G  0 disk /var/lib/directpv/mnt/ab82b195-5204-47fa-a864-a351f66b38d2
nvme2n1      259:4    0   30G  0 disk /var/lib/directpv/mnt/94be15e6-5d0d-49a9-a6d8-d139977a3446

k get directpvdrives,directpvvolumes

#
cat << EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nginx-pvc
spec:
  volumeMode: Filesystem
  storageClassName: directpv-min-io
  accessModes: [ "ReadWriteOnce" ]
  resources:
    requests:
      storage: 8Mi
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod
spec:
  volumes:
    - name: nginx-volume
      persistentVolumeClaim:
        claimName: nginx-pvc
  containers:
    - name: nginx-container
      image: nginx:alpine
      volumeMounts:
        - mountPath: "/mnt"
          name: nginx-volume
EOF

#
k get pod,pvc,pv
NAME            READY   STATUS    RESTARTS   AGE
pod/nginx-pod   1/1     Running   0          2m27s

NAME                              STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/nginx-pvc   Bound    pvc-97418e78-41db-4244-a3d5-e56b94c6c2bd   8Mi        RWO            directpv-min-io   <unset>                 2m27s

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS      VOLUMEATTRIBUTESCLASS   REASON   AGE
persistentvolume/pvc-97418e78-41db-4244-a3d5-e56b94c6c2bd   8Mi        RWO            Delete           Bound    default/nginx-pvc   directpv-min-io   <unset>                          2m27s

k exec -it nginx-pod -- df -hT -t xfs
Filesystem           Type            Size      Used Available Use% Mounted on
/dev/nvme3n1         xfs             8.0M      4.0K      8.0M   0% /mnt

k exec -it nginx-pod -- sh -c 'echo hello > /mnt/hello.txt'
k exec -it nginx-pod -- sh -c 'cat /mnt/hello.txt'
hello

#
lsblk
nvme1n1      259:0    0   30G  0 disk /var/lib/directpv/mnt/dc26b422-5364-464e-bc54-a6ab6b8d4e32
nvme4n1      259:3    0   30G  0 disk /var/lib/directpv/mnt/ab82b195-5204-47fa-a864-a351f66b38d2
nvme2n1      259:4    0   30G  0 disk /var/lib/directpv/mnt/94be15e6-5d0d-49a9-a6d8-d139977a3446
nvme3n1      259:2    0   30G  0 disk /var/lib/kubelet/pods/171f29cf-45e1-458f-b20e-082a78702e03/volumes/kubernetes.io~csi/pvc-97418e78-41db-4244-a3d5-e56b94c6c2bd/mount
                                      /var/lib/kubelet/plugins/kubernetes.io/csi/directpv-min-io/0a26dd2aeb3b456582028ecddabce74e4684252502203e78f857fa23e738649b/globalmount
                                      /var/lib/directpv/mnt/246f427a-3e32-4311-b81c-fc9e45ebc6ad

tree -a /var/lib/directpv/mnt
/var/lib/directpv/mnt
├── 246f427a-3e32-4311-b81c-fc9e45ebc6ad
│   ├── .FSUUID.246f427a-3e32-4311-b81c-fc9e45ebc6ad -> .
│   ├── .directpv
│   │   └── meta.info
│   └── pvc-97418e78-41db-4244-a3d5-e56b94c6c2bd
│       └── hello.txt
├── 94be15e6-5d0d-49a9-a6d8-d139977a3446
│   ├── .FSUUID.94be15e6-5d0d-49a9-a6d8-d139977a3446 -> .
│   └── .directpv
│       └── meta.info
├── ab82b195-5204-47fa-a864-a351f66b38d2
│   ├── .FSUUID.ab82b195-5204-47fa-a864-a351f66b38d2 -> .
│   └── .directpv
│       └── meta.info
└── dc26b422-5364-464e-bc54-a6ab6b8d4e32
    ├── .FSUUID.dc26b422-5364-464e-bc54-a6ab6b8d4e32 -> .
    └── .directpv
        └── meta.info
        
cat /var/lib/directpv/mnt/*/pvc*/hello.txt
hello

#
k get directpvvolumes
k get directpvvolumes -o yaml | yq


#
k delete pod nginx-pod
k get pvc,pv
k delete pvc nginx-pvc
k get pv

#
lsblk
tree -a /var/lib/directpv/mnt

 

특정 Drive에 볼륨 사용 테스트(label)

#
k directpv list drives
┌───────┬─────────┬────────────────────────────┬────────┬────────┬─────────┬────────┐
│ NODE  │ NAME    │ MAKE                       │ SIZE   │ FREE   │ VOLUMES │ STATUS │
├───────┼─────────┼────────────────────────────┼────────┼────────┼─────────┼────────┤
│ k3s-s │ nvme1n1 │ Amazon Elastic Block Store │ 30 GiB │ 30 GiB │ -       │ Ready  │
│ k3s-s │ nvme2n1 │ Amazon Elastic Block Store │ 30 GiB │ 30 GiB │ -       │ Ready  │
│ k3s-s │ nvme3n1 │ Amazon Elastic Block Store │ 30 GiB │ 30 GiB │ -       │ Ready  │
│ k3s-s │ nvme4n1 │ Amazon Elastic Block Store │ 30 GiB │ 30 GiB │ -       │ Ready  │
└───────┴─────────┴────────────────────────────┴────────┴────────┴─────────┴────────┘

# Label the 'nvme1n1' drive in all nodes as 'fast' with the 'tier' key.
kubectl directpv label drives --drives=nvme1n1 tier=fast

# Verify that the labels are properly set by using the list command with the
k directpv list drives --show-labels
kubectl directpv list drives --drives /dev/nvme1n1 --show-labels
┌───────┬─────────┬────────────────────────────┬────────┬────────┬─────────┬────────┬───────────┐
│ NODE  │ NAME    │ MAKE                       │ SIZE   │ FREE   │ VOLUMES │ STATUS │ LABELS    │
├───────┼─────────┼────────────────────────────┼────────┼────────┼─────────┼────────┼───────────┤
│ k3s-s │ nvme1n1 │ Amazon Elastic Block Store │ 30 GiB │ 30 GiB │ -       │ Ready  │ tier=fast │
└───────┴─────────┴────────────────────────────┴────────┴────────┴─────────┴────────┴───────────┘

#
k get sc
vi create-storage-class.sh
chmod +x create-storage-class.sh
./create-storage-class.sh -h

#
./create-storage-class.sh fast-tier-storage 'directpv.min.io/tier: fast'
k get sc
kc describe sc fast-tier-storage
...
Parameters:            directpv.min.io/tier=fast,fstype=xfs
...

#
cat << EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nginx-pvc
spec:
  volumeMode: Filesystem
  storageClassName: fast-tier-storage
  accessModes: [ "ReadWriteOnce" ]
  resources:
    requests:
      storage: 8Mi
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod
spec:
  volumes:
    - name: nginx-volume
      persistentVolumeClaim:
        claimName: nginx-pvc
  containers:
    - name: nginx-container
      image: nginx:alpine
      volumeMounts:
        - mountPath: "/mnt"
          name: nginx-volume
EOF

# 해당 drive가 있는 node 에 pv 가 만들어지고, 파드 역시 해당 node 에 기동되었음
k get pod,pvc,pv -owide
NAME            READY   STATUS    RESTARTS   AGE   IP           NODE    NOMINATED NODE   READINESS GATES
pod/nginx-pod   1/1     Running   0          11s   10.42.0.13   k3s-s   <none>           <none>

NAME                              STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS        VOLUMEATTRIBUTESCLASS   AGE   VOLUMEMODE
persistentvolumeclaim/nginx-pvc   Bound    pvc-127ee2bb-b4b6-4cc5-bfdd-d79b2e7e5957   8Mi        RWO            fast-tier-storage   <unset>                 11s   Filesystem

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS        VOLUMEATTRIBUTESCLASS   REASON   AGE   VOLUMEMODE
persistentvolume/pvc-127ee2bb-b4b6-4cc5-bfdd-d79b2e7e5957   8Mi        RWO            Delete           Bound    default/nginx-pvc   fast-tier-storage   <unset>                          11s   Filesystem

kc describe pv
...
Node Affinity:
  Required Terms:
    Term 0:        directpv.min.io/identity in [directpv-min-io]
                   directpv.min.io/node in [k3s-s]
                   directpv.min.io/rack in [default]
                   directpv.min.io/region in [default]
                   directpv.min.io/zone in [default]
Message:
Source:
    Type:              CSI (a Container Storage Interface (CSI) volume source)
    Driver:            directpv-min-io
    FSType:            xfs
    VolumeHandle:      pvc-127ee2bb-b4b6-4cc5-bfdd-d79b2e7e5957
    ReadOnly:          false
    VolumeAttributes:      directpv.min.io/tier=fast
                           fstype=xfs
                           storage.kubernetes.io/csiProvisionerIdentity=1757816235335-1863-directpv-min-io

#
kc describe node
...
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/instance-type=k3s
                    beta.kubernetes.io/os=linux
                    directpv.min.io/identity=directpv-min-io
                    directpv.min.io/node=k3s-s
                    directpv.min.io/rack=default
                    directpv.min.io/region=default
                    directpv.min.io/zone=default
...

#
k get directpvnodes.directpv.min.io -o yaml | yq
kc describe directpvdrives.directpv.min.io
Name:         dc26b422-5364-464e-bc54-a6ab6b8d4e32
Namespace:
Labels:       directpv.min.io/access-tier=Default
              directpv.min.io/created-by=directpv-driver
              directpv.min.io/drive-name=nvme1n1
              directpv.min.io/node=k3s-s
              directpv.min.io/tier=fast
              directpv.min.io/version=v1beta1

# 삭제
kubectl delete pod nginx-pod && kubectl delete pvc nginx-pvc

 

 

 

라벨 활용법

#
k label nodes k3s-s directpv.min.io/rack=rack01 --overwrite
node/k3s-s labeled

kc describe node
...
                    directpv.min.io/rack=rack01

kc describe directpvdrives.directpv.min.io
...
 Topology:
   directpv.min.io/rack:      default

#
cat << EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nginx-pvc
spec:
  volumeMode: Filesystem
  storageClassName: fast-tier-storage
  accessModes: [ "ReadWriteOnce" ]
  resources:
    requests:
      storage: 8Mi
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod
spec:
  volumes:
    - name: nginx-volume
      persistentVolumeClaim:
        claimName: nginx-pvc
  containers:
    - name: nginx-container
      image: nginx:alpine
      volumeMounts:
        - mountPath: "/mnt"
          name: nginx-volume
EOF

#
k get pod,pvc,pv
NAME            READY   STATUS    RESTARTS   AGE
pod/nginx-pod   0/1     Pending   0          4s

NAME                              STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS        VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/nginx-pvc   Pending                                      fast-tier-storage   <unset>                 4s

#
kc describe pod
...
Events:
  Type     Reason            Age               From               Message
  ----     ------            ----              ----               -------
  Warning  FailedScheduling  15s               default-scheduler  running PreBind plugin "VolumeBinding": binding volumes: provisioning failed for PVC "nginx-pvc"
  Warning  FailedScheduling  9s (x2 over 13s)  default-scheduler  running PreBind plugin "VolumeBinding": binding volumes: provisioning failed for PVC "nginx-pvc"

# no drive found for requested topology
kc describe pvc
...
Events:
  Type     Reason                Age                From                                                                              Message
  ----     ------                ----               ----                                                                              -------
  Normal   WaitForFirstConsumer  66s                persistentvolume-controller                                                       waiting for first consumer to be created before binding
  Normal   ExternalProvisioning  7s (x9 over 66s)   persistentvolume-controller                                                       Waiting for a volume to be created either by the external provisioner 'directpv-min-io' or manually by the system administrator. If volume creation is delayed, please verify that the provisioner is running and correctly registered.
  Normal   Provisioning          7s (x8 over 66s)   directpv-min-io_controller-75d5595d5b-bqwbs_4e34d69c-36a7-4c94-8ce0-a02df9c63fc8  External provisioner is provisioning volume for claim "default/nginx-pvc"
  Warning  ProvisioningFailed    7s (x8 over 66s)   directpv-min-io_controller-75d5595d5b-bqwbs_4e34d69c-36a7-4c94-8ce0-a02df9c63fc8  failed to provision volume with StorageClass "fast-tier-storage": rpc error: code = ResourceExhausted desc = no drive found for requested topology; requested node(s): k3s-s; requested size: 8388608 bytes

# 해결 1 : label 값 원복
k label nodes k3s-s directpv.min.io/rack=default --overwrite

# 해결 2 : directpvdrives CR에 label 값 directpv.min.io/rack=rack01 로 수정

# 확인
k get pod,pvc,pv

# 삭제
kubectl delete pod nginx-pod && kubectl delete pvc nginx-pvc

 

  Warning  ProvisioningFailed    7s (x5 over 30s)  directpv-min-io_controller-5db75d68cc-tx8vw_4d6e6fba-d62e-4a05-bd6c-82fc8a68a4c9  failed to provision volume with StorageClass "fast-tier-storage": rpc error: code = ResourceExhausted desc = no drive found for requested topology; requested node(s): k3s-s; requested size: 8388608 bytes

 

 

 

MinIO 설치  & 버킷 생성

#
helm repo add minio-operator https://operator.min.io

# https://github.com/minio/operator/blob/master/helm/operator/values.yaml
cat << EOF > minio-operator-values.yaml
operator:  
  env:
  - name: MINIO_OPERATOR_RUNTIME
    value: "Rancher"
  replicaCount: 1
EOF
helm install --namespace minio-operator --create-namespace minio-operator minio-operator/operator --values minio-operator-values.yaml

# 확인 : 참고로 현재는 오퍼레이터 관리 웹 미제공
k get-all -n minio-operator
k get pod,svc,ep -n minio-operator
k get crd
k exec -it -n minio-operator deploy/minio-operator -- env | grep MINIO
MINIO_OPERATOR_RUNTIME=Rancher

------------------------------------------------------
# If using Amazon Elastic Block Store (EBS) CSI driver : Please make sure to set xfs for "csi.storage.k8s.io/fstype" parameter under StorageClass.parameters.
k get sc directpv-min-io -o yaml | grep -i fstype
  csi.storage.k8s.io/fstype: xfs

  
# tenant values : https://github.com/minio/operator/blob/master/helm/tenant/values.yaml
cat << EOF > minio-tenant-1-values.yaml
tenant:
  name: tenant1

  configSecret:
    name: tenant1-env-configuration
    accessKey: minio
    secretKey: minio123

  pools:
    - servers: 1
      name: pool-0
      volumesPerServer: 4
      size: 10Gi 
      storageClassName: directpv-min-io
  env:
    - name: MINIO_STORAGE_CLASS_STANDARD
      value: "EC:1"

  metrics:
    enabled: true
    port: 9000
    protocol: http
EOF
helm install --namespace tenant1 --create-namespace --values minio-tenant-1-values.yaml tenant1 minio-operator/tenant \
 && kubectl get tenants -A -w

#
kubectl get tenants -n tenant1
kubectl get tenants -n tenant1 -owide -o yaml | yq
kc describe tenants -n tenant1

#
lsblk
k directpv info
k directpv list drives
k directpv list volumes
┌──────────────────────────────────────────┬──────────┬───────┬─────────┬──────────────────┬──────────────┬─────────┐
│ VOLUME                                   │ CAPACITY │ NODE  │ DRIVE   │ PODNAME          │ PODNAMESPACE │ STATUS  │
├──────────────────────────────────────────┼──────────┼───────┼─────────┼──────────────────┼──────────────┼─────────┤
│ pvc-ae274681-c458-409c-a61c-a018f26e2580 │ 10 GiB   │ k3s-s │ nvme1n1 │ myminio-pool-0-0 │ tenant-0     │ Bounded │
│ pvc-8f8df2f7-7f93-4531-b0da-1dbc8affa8df │ 10 GiB   │ k3s-s │ nvme2n1 │ myminio-pool-0-0 │ tenant-0     │ Bounded │
│ pvc-0ab32dfd-0330-405e-8902-78f917bd71ec │ 10 GiB   │ k3s-s │ nvme3n1 │ myminio-pool-0-0 │ tenant-0     │ Bounded │
│ pvc-bfc21d48-87aa-4bf0-ab3f-c1b9acd6b716 │ 10 GiB   │ k3s-s │ nvme4n1 │ myminio-pool-0-0 │ tenant-0     │ Bounded │
└──────────────────────────────────────────┴──────────┴───────┴─────────┴──────────────────┴──────────────┴─────────┘

k get directpvvolumes.directpv.min.io
k get directpvvolumes.directpv.min.io -o yaml | yq
kc describe directpvvolumes
tree -ah /var/lib/kubelet/plugins
tree -ah /var/lib/directpv/mnt
cat /var/lib/kubelet/plugins/kubernetes.io/csi/directpv-min-io/*/vol_data.json
{"driverName":"directpv-min-io","volumeHandle":"pvc-0ab32dfd-0330-405e-8902-78f917bd71ec"}
{"driverName":"directpv-min-io","volumeHandle":"pvc-8f8df2f7-7f93-4531-b0da-1dbc8affa8df"}
{"driverName":"directpv-min-io","volumeHandle":"pvc-ae274681-c458-409c-a61c-a018f26e2580"}
{"driverName":"directpv-min-io","volumeHandle":"pvc-bfc21d48-87aa-4bf0-ab3f-c1b9acd6b716"}

#
k get pvc -n tenant1
k get pvc -n tenant1 -o yaml | yq
kc describe pvc -n tenant1


#
kubectl get sts,pod,svc,ep,pvc,secret -n tenant1
kubectl get pod -n tenant1 -l v1.min.io/pool=pool-0 -owide
kc describe pod -n tenant1 -l v1.min.io/pool=pool-0
kubectl stern -n tenant1 -l v1.min.io/pool=pool-0
kubectl exec -it -n tenant1 sts/tenant1-pool-0 -c minio -- id
kubectl exec -it -n tenant1 sts/tenant1-pool-0 -c minio -- env
kubectl exec -it -n tenant1 sts/tenant1-pool-0 -c minio -- cat /tmp/minio/config.env
kubectl get secret -n tenant1 tenant1-env-configuration -o jsonpath='{.data.config\.env}' | base64 -d ; echo
kubectl get secret -n tenant1 tenant1-tls -o jsonpath='{.data.public\.crt}' | base64 -d
kubectl get secret -n tenant1 tenant1-tls -o jsonpath='{.data.public\.crt}' | base64 -d | openssl x509 -noout -text
...
            X509v3 Subject Alternative Name:
                DNS:tenant1-pool-0-0.tenant1-hl.tenant1.svc.cluster.local, DNS:minio.tenant1.svc.cluster.local, DNS:minio.tenant1, DNS:minio.tenant1.svc, DNS:*., DNS:*.tenant1.svc.cluster.local
                
#
kubectl patch svc -n tenant1 tenant1-console -p '{"spec": {"type": "NodePort", "ports": [{"port": 9443, "targetPort": 9443, "nodePort": 30001}]}}'

# 기본키(minio , minio123)
echo "https://$(curl -s ipinfo.io/ip):30001"

#
kubectl patch svc -n tenant1 minio -p '{"spec": {"type": "NodePort", "ports": [{"port": 443, "targetPort": 9000, "nodePort": 30002}]}}'


# mc alias
mc alias set k8s-tenant1 https://127.0.0.1:30002 minio minio123 --insecure
mc alias list
mc admin info k8s-tenant1 --insecure

# 버킷 생성
mc mb k8s-tenant1/mybucket --insecure
mc ls k8s-tenant1 --insecure

 

 

 

 

# mc alias
mc alias set k8s-tenant1 https://127.0.0.1:30002 minio minio123 --insecure
mc alias list
...
k8s-tenant1
  URL       : https://127.0.0.1:30002
  AccessKey : minio
  SecretKey : minio123
  API       : s3v4
  Path      : auto
  Src       : /root/.mc/config.json
...

tree -a ~/.mc
/root/.mc
├── certs # certificates
│   └── CAs # Certificate Authorities
├── config.json 
├── config.json.old
└── share
    ├── downloads.json
    └── uploads.json
    
mc admin info k8s-tenant1 --insecure

# 버킷 생성
mc mb k8s-tenant1/mybucket --insecure
mc ls k8s-tenant1 --insecure

# 바로 위에서 설정한 내용
------------------------------------------
# 아래 부터 실습 진행

# [신규 터미널] 모니터링 Show HTTP call trace for all incoming and internode on MinIO
# https://docs.min.io/community/minio-object-store/reference/minio-mc-admin/mc-admin-trace.html
mc admin trace -v -a k8s-tenant1 --insecure
# mc admin trace --errors -v -a k8s-tenant1 --insecure # 4xx, 5xx 에러만 보기
     
# https://docs.min.io/community/minio-object-store/reference/minio-mc/mc-ls.html
mc ls --summarize --recursive k8s-tenant1 --insecure

# https://docs.min.io/community/minio-object-store/reference/minio-mc/mc-cat.html
echo hello > hello.txt
mc cp ./hello.txt k8s-tenant1/mybucket/ --insecure
mc cat k8s-tenant1/mybucket/hello.txt --insecure

# 100MB 파일 생성
</dev/urandom tr -dc 'A-Za-z0-9' | head -c 100M > randtext.txt
ls -alh randtext.txt
-rw-r--r-- 1 root root 100M Sep 14 15:08 randtext.txt

cat randtext.txt | md5sum

# https://docs.min.io/community/minio-object-store/reference/minio-mc/mc-cp.html
mc cp ./randtext.txt k8s-tenant1/mybucket/ --insecure
/root/randtext.txt:     100.00 MiB / 100.00 MiB ┃▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓┃ 320.47 MiB/s 0s

mc cat k8s-tenant1/mybucket/randtext.txt --insecure | md5sum

# MinIO Multipart Upload는 '큰 파일을 S3 방식으로 업로드할 때 쪼개서 올리고, 다 올리면 서버가 합쳐주는' 기능
[Client] ---CreateMultipartUpload---> [MinIO]
         <--- UploadId ---------------
[Client] ---UploadPart(part1)--------> [MinIO]
[Client] ---UploadPart(part2)--------> [MinIO]
...
[Client] ---CompleteMultipartUpload--> [MinIO]
         <--- OK + Final Object -------
mc admin trace -v -a k8s-tenant1 --insecure
127.0.0.1:30002 [REQUEST s3.CompleteMultipartUpload] [2025-09-14T15:11:04.710] [Client IP: 10.42.0.1]
127.0.0.1:30002 POST /mybucket/randtext.txt?uploadId=ZTMyZjNlMjAtZDkyNi00YjcyLTlhNGItZTI4ODBiNzBmZTM1LmY3N2ZiOTY0LWM5NmItNDE5YS1iNThmLWY3ODAyM2FmY2JjM3gxNzU3ODMwMjY0NDE5MzA0NDQ1
127.0.0.1:30002 Proto: HTTP/1.1
127.0.0.1:30002 Host: 127.0.0.1:30002
127.0.0.1:30002 Content-Type: application/octet-stream
127.0.0.1:30002 User-Agent: MinIO (linux; amd64) minio-go/v7.0.90 mc/RELEASE.2025-08-13T08-35-41Z
127.0.0.1:30002 X-Amz-Content-Sha256: 159a60374060e572e313b089f1d4658285b3676e38c08cdba940c0342f7431a9
127.0.0.1:30002 X-Amz-Date: 20250914T061104Z
127.0.0.1:30002 Accept-Encoding: zstd,gzip
127.0.0.1:30002 Authorization: AWS4-HMAC-SHA256 Credential=minio/20250914/us-east-1/s3/aws4_request, SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date, Signature=3006225675b1e83ec73a4acdc49e14912ea547e075be739cc9a580ebde31a858
127.0.0.1:30002 Content-Length: 687
127.0.0.1:30002 <CompleteMultipartUpload xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Part><PartNumber>1</PartNumber><ETag>2e7fda86cc02f78e25480c65efb94a29</ETag></Part><Part><PartNumber>2</PartNumber><ETag>e2b66d6f0368ce9a13de6ca12ed78a90</ETag></Part><Part><PartNumber>3</PartNumber><ETag>7b0ae1958b0f62992b13dfe4a7dddf07</ETag></Part><Part><PartNumber>4</PartNumber><ETag>942130b42c320b33a77ad9812416546b</ETag></Part><Part><PartNumber>5</PartNumber><ETag>81bea9efbeb94cadacb210343698806d</ETag></Part><Part><PartNumber>6</PartNumber><ETag>89c6655445c13b36c83a37b8b8e589c1</ETag></Part><Part><PartNumber>7</PartNumber><ETag>084d78f3c6126f91dc72c1933754ec46</ETag></Part></CompleteMultipartUpload>

127.0.0.1:30002 [RESPONSE] [2025-09-14T15:11:04.717] [ Duration 7.469ms TTFB 7.391942ms ↑ 793 B  ↓ 321 B ]
...
<CompleteMultipartUploadResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Location>https://127.0.0.1:30002/mybucket/randtext.txt</Location><Bucket>mybucket</Bucket><Key>randtext.txt</Key><ETag>&#34;2ab7293624b88db302add6a2cacdd804-7&#34;</ETag></CompleteMultipartUploadResult>
# 참고) put https://docs.min.io/community/minio-object-store/reference/minio-mc/mc-put.html


#
cp randtext.txt randtext2.txt
mc cp ./randtext2.txt k8s-tenant1/mybucket/ --disable-multipart --insecure
/root/randtext2.txt:    100.00 MiB / 100.00 MiB ┃▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓┃ 290.70 MiB/s 0s

#
mc ls --summarize --recursive --versions k8s-tenant1 --insecure
[2025-09-14 14:36:17 KST]     0B mybucket/
[2025-09-14 15:11:04 KST] 100MiB STANDARD null v1 PUT mybucket/randtext.txt
[2025-09-14 15:17:43 KST] 100MiB STANDARD null v1 PUT mybucket/randtext2.txt
Total Size: 200 MiB
Total Objects: 3

# 
k exec -it -n tenant1 tenant1-pool-0-0 -c minio -- sh -c 'df -hT --type xfs'
Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/nvme2n1   xfs    10G   67M   10G   1% /export0
/dev/nvme1n1   xfs    10G   67M   10G   1% /export1
/dev/nvme3n1   xfs    10G   67M   10G   1% /export2
/dev/nvme4n1   xfs    10G   67M   10G   1% /export3

k exec -it -n tenant1 tenant1-pool-0-0 -c minio -- ls -R /export0
k exec -it -n tenant1 tenant1-pool-0-0 -c minio -- ls -R /export1
k exec -it -n tenant1 tenant1-pool-0-0 -c minio -- ls -R /export2
k exec -it -n tenant1 tenant1-pool-0-0 -c minio -- ls -R /export3

#
tree -a /var/lib/directpv/mnt
...
            └── mybucket
                ├── randtext.txt
                │   ├── 13a96fc7-0dc5-48c2-b949-24ec24322d31
                │   │   ├── part.1
                │   │   ├── part.2
                │   │   ├── part.3
                │   │   ├── part.4
                │   │   ├── part.5
                │   │   ├── part.6
                │   │   └── part.7
                │   └── xl.meta
                └── randtext2.txt
                    ├── 287967c0-a38e-4734-b797-36ea1c76147a
                    │   └── part.1
                    └── xl.meta

# rm - https://docs.min.io/community/minio-object-store/reference/minio-mc/mc-rm.html
mc rm k8s-tenant1/mybucket/randtext2.txt --insecure
127.0.0.1:30002 [REQUEST s3.DeleteMultipleObjects] [2025-09-14T15:34:27.955] [Client IP: 10.42.0.1]
127.0.0.1:30002 POST /mybucket/?delete=
...

#
mc cp k8s-tenant1/mybucket/randtext.txt ./randtext3.txt  --insecure
127.0.0.1:9000  [OS os.OpenFileR] [2025-09-14T15:46:10.530] /export3/data/mybucket/randtext.txt/13a96fc7-0dc5-48c2-b949-24ec24322d31/part.4 25.794µs
127.0.0.1:9000  [OS os.OpenFileR] [2025-09-14T15:46:10.530] /export1/data/mybucket/randtext.txt/13a96fc7-0dc5-48c2-b949-24ec24322d31/part.4 32.617µs
127.0.0.1:9000  [STORAGE storage.ReadFileStream] [2025-09-14T15:46:10.530] /export3/data mybucket randtext.txt/13a96fc7-0dc5-48c2-b949-24ec24322d31/part.4 total-errs-availability=0 total-errs-timeout=0 54.316µs 5.3 MiB
127.0.0.1:9000  [STORAGE storage.ReadFileStream] [2025-09-14T15:46:10.530] /export1/data mybucket randtext.txt/13a96fc7-0dc5-48c2-b949-24ec24322d31/part.4 total-errs-timeout=0 total-errs-availability=0 63.241µs 5.3 MiB
...
127.0.0.1:30002 [REQUEST s3.GetObject] [2025-09-14T15:46:10.435] [Client IP: 10.42.0.1]
127.0.0.1:30002 GET /mybucket/randtext.txt
127.0.0.1:30002 Proto: HTTP/1.1
127.0.0.1:30002 Host: 127.0.0.1:30002
127.0.0.1:30002 Accept-Encoding: identity
127.0.0.1:30002 Authorization: AWS4-HMAC-SHA256 Credential=minio/20250914/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=ae36a34bc4a7db66f08970b3f295da8084d03236448a04d23a23b79c87026710
127.0.0.1:30002 Content-Length: 0
127.0.0.1:30002 User-Agent: MinIO (linux; amd64) minio-go/v7.0.90 mc/RELEASE.2025-08-13T08-35-41Z
127.0.0.1:30002 X-Amz-Content-Sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
127.0.0.1:30002 X-Amz-Date: 20250914T064610Z
127.0.0.1:30002 <BLOB>
127.0.0.1:30002 [RESPONSE] [2025-09-14T15:46:10.645] [ Duration 209.83ms TTFB 4.247996ms ↑ 93 B  ↓ 100 MiB ]
...

mc ls, cp, rm 실습

 

파드 재기동 없이 볼륨 확장

#
kubectl get pvc -n tenant1

k directpv list drives
┌───────┬─────────┬────────────────────────────┬────────┬────────┬─────────┬────────┐
│ NODE  │ NAME    │ MAKE                       │ SIZE   │ FREE   │ VOLUMES │ STATUS │
├───────┼─────────┼────────────────────────────┼────────┼────────┼─────────┼────────┤
│ k3s-s │ nvme1n1 │ Amazon Elastic Block Store │ 30 GiB │ 20 GiB │ 1       │ Ready  │
│ k3s-s │ nvme2n1 │ Amazon Elastic Block Store │ 30 GiB │ 20 GiB │ 1       │ Ready  │
│ k3s-s │ nvme3n1 │ Amazon Elastic Block Store │ 30 GiB │ 20 GiB │ 1       │ Ready  │
│ k3s-s │ nvme4n1 │ Amazon Elastic Block Store │ 30 GiB │ 20 GiB │ 1       │ Ready  │
└───────┴─────────┴────────────────────────────┴────────┴────────┴─────────┴────────┘

k directpv info
┌─────────┬──────────┬───────────┬─────────┬────────┐
│ NODE    │ CAPACITY │ ALLOCATED │ VOLUMES │ DRIVES │
├─────────┼──────────┼───────────┼─────────┼────────┤
│ • k3s-s │ 120 GiB  │ 40 GiB    │ 4       │ 4      │
└─────────┴──────────┴───────────┴─────────┴────────┘

#
kubectl patch pvc -n tenant1 data0-tenant1-pool-0-0 -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'
kubectl patch pvc -n tenant1 data1-tenant1-pool-0-0 -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'
kubectl patch pvc -n tenant1 data2-tenant1-pool-0-0 -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'
kubectl patch pvc -n tenant1 data3-tenant1-pool-0-0 -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'

#
kubectl get pvc -n tenant1

k directpv list drives
┌───────┬─────────┬────────────────────────────┬────────┬────────┬─────────┬────────┐
│ NODE  │ NAME    │ MAKE                       │ SIZE   │ FREE   │ VOLUMES │ STATUS │
├───────┼─────────┼────────────────────────────┼────────┼────────┼─────────┼────────┤
│ k3s-s │ nvme1n1 │ Amazon Elastic Block Store │ 30 GiB │ 10 GiB │ 1       │ Ready  │
│ k3s-s │ nvme2n1 │ Amazon Elastic Block Store │ 30 GiB │ 10 GiB │ 1       │ Ready  │
│ k3s-s │ nvme3n1 │ Amazon Elastic Block Store │ 30 GiB │ 10 GiB │ 1       │ Ready  │
│ k3s-s │ nvme4n1 │ Amazon Elastic Block Store │ 30 GiB │ 10 GiB │ 1       │ Ready  │
└───────┴─────────┴────────────────────────────┴────────┴────────┴─────────┴────────┘

k directpv info
┌─────────┬──────────┬───────────┬─────────┬────────┐
│ NODE    │ CAPACITY │ ALLOCATED │ VOLUMES │ DRIVES │
├─────────┼──────────┼───────────┼─────────┼────────┤
│ • k3s-s │ 120 GiB  │ 80 GiB    │ 4       │ 4      │
└─────────┴──────────┴───────────┴─────────┴────────┘

# 아래 파드 내에서 볼륨 Size 가 20G 로 조금 시간 지나면 자동 확장 반영 된다.
k exec -it -n tenant1 tenant1-pool-0-0 -c minio -- sh -c 'df -hT --type xfs'
Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/nvme1n1   xfs    10G   34M   10G   1% /export0
/dev/nvme2n1   xfs    10G   34M   10G   1% /export1
/dev/nvme3n1   xfs    10G   34M   10G   1% /export2
/dev/nvme4n1   xfs    10G   34M   10G   1% /export3

k exec -it -n tenant1 tenant1-pool-0-0 -c minio -- sh -c 'df -hT --type xfs'
Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/nvme1n1   xfs    20G   34M   20G   1% /export0
/dev/nvme2n1   xfs    20G   34M   20G   1% /export1
/dev/nvme3n1   xfs    20G   34M   20G   1% /export2
/dev/nvme4n1   xfs    20G   34M   20G   1% /export3

 

 

Perfomance & Warp

- 출시 시 저장할 테비바이트 단위의 예상 데이터 양
- 향후 최소 2년간 데이터 크기의 예상 성장률
- 평균 객체 크기별 객체 수
- 데이터의 평균 보존 시간(년)
- 배포할 사이트 수
- 예상 버킷 수

 

 

  • 네트워킹은 MinIO 성능에 가장 큰 영향을 미치는데, 호스트당 낮은 대역폭은 스토리지의 잠재적 성능을 인위적으로 제한하기 때문입니다.
  • 다음 네트워크 처리량 제약 조건 예시에서는 ~100MB/S의 지속 I/O를 제공하는 회전 디스크를 가정합니다.
    • 1GbE 네트워크 링크는 최대 125MB/s 또는 회전 디스크 1개를 지원할 수 있습니다.
    • 10GbE 네트워크는 약 1.25GB/s를 지원하여 잠재적으로 10~12개의 회전 디스크를 지원할 수 있습니다.
    • 25GbE 네트워크는 약 3.125GB/s를 지원하여 잠재적으로 약 30개의 회전 디스크를 지원할 수 있습니다.

 

1. 네트워크 (100GbE) 병목

네트워크 카드가 데이터를 주고받는 속도는 처음부터 정해진 최대치가 있습니다.
100GbE(기가비트 이더넷) 카드 한 개는 초당 약 11.4GB의 데이터를 처리할 수 있고, 두 개 포트가 있으면 이론상 22.8GB/s까지 올라갑니다.
하지만 실제로는 네트워크 프로토콜(데이터를 안전하게 보내는 여러 규칙)에도 시간이 들고, 장비에 따라 성능 차이가 있어 명목 속도보다 느릴 수 있습니다.

 

2. 네트워크 카드와 메인보드 연결(PCIe 버스) 병목

네트워크 카드는 CPU와 직접 연결되지 않고, 컴퓨터 내부의 고속 통로(PCIe 슬롯)를 이용합니다.
100GbE 듀얼 포트 카드를 이 고속 통로(PCIe 3.0 x16)에 연결하면, 이 통로가 처리할 수 있는 최대 속도(14.5GB/s)보다 카드에서 쏟아내는 데이터가 더 많아서, 실제 전체 속도가 늦어질 수 있습니다.
즉, 데이터가 교차로에서 밀려 차가 막히듯, 내부 회선에서 좁아지는 구간이 생기는 것입니다.

 

3. 프로세서(코어/클럭) 병목

NVMe SSD는 CPU와 바로 연결되어 빠릅니다.
하지만 데이터를 처리하는 건 결국 CPU(프로세서)입니다.
각 디스크나 네트워크에서 들어오는 작업을 처리할 '두뇌(코어/클럭)'가 부족하면, 아무리 빠른 SSD라도 실제 속도가 떨어집니다.
실험상, 디스크 1개당 CPU코어 2개(2.5GHz 이상)가 있어야 SSD의 최대 속도를 쓸 수 있다는 결과가 있습니다.

 

4. 프로세서 간(소켓 간) 연결 병목 (NUMA, UPI 등)

최신 서버는 두 개 이상의 CPU(소켓)를 쓰는 경우가 많습니다.
서로 다른 CPU에 연결된 메모리·디스크·네트워크 장치가 있을 때, 이들 사이를 잇는 연결선(UPI 등)을 통해 데이터가 오가야 합니다.
이 연결 과정에서 지연이 생길 수 있는데, 장치를 같은 CPU 옆에 놓고 쓰면 빠르지만, 반대쪽 CPU와 연동하는 경우 느려집니다.

 

5. PCIe 스위치 병목

NVMe SSD가 많이 장착된 서버는 PCIe 확장용 스위치(중간 연결장치)를 씁니다.
스위치 한 개가 한꺼번에 관리할 수 있는 PCIe 대역폭엔 한계가 있으므로, SSD 12개가 한 스위치로 묶이면, 모든 SSD가 풀 성능을 낼 수는 없습니다.
즉, 모두가 동시에 빠른 속도로 데이터 전송을 시도하면, 중간 허브가 '나눠쓰기'가 되어 성능이 줄어듭니다.

 

6. NVMe 드라이브와 PCIe 연결 병목

각 NVMe SSD는 PC 내부 통로(PCIe) 4개 레인으로 연결되어 있고, 이론적으로 양방향 3.6GB/s를 지원합니다.
대부분의 일반적인 작업 환경에서는 이 정도면 충분하며, SSD 한 개만 독립적으로 사용할 때는 병목이 거의 없습니다.

 

7. NVMe 드라이브 자체 병목

SSD 각각은 여러 메모리칩(Cell)이 있어, 동시에 읽고 쓰기를 할 수 있습니다.
하지만, 각 SSD가 가진 설계나 내부 구조(분할 방식)에 따라 다 같이 완벽하게 빨라지진 않습니다.
동시에 여러 작업이 몰리거나, 저장 공간이 거의 찼을 때 성능 저하가 나타날 수 있습니다.
여러 SSD가 한 PCIe 스위치를 공유하면, 위에서 언급한 스위치 병목도 함께 작동합니다.

 

 

PCIe 정보 확인

# 각 PCI 디바이스의 Link Capabilities / Link Status 섹션에 현재 Lane과 속도가 표시
lspci -vv | grep -A40 -i "nvme"
예시)
LnkCap: Port #8, Speed 16GT/s, Width x16 # LnkCap = 최대 지원 속도/레인
LnkSta: Speed 8GT/s, Width x8            # LnkSta = 현재 동작 속도/레인

00:1f.0 Non-Volatile memory controller: Amazon.com, Inc. NVMe EBS Controller (prog-if 02 [NVM Express])
	Subsystem: Amazon.com, Inc. NVMe EBS Controller
	Physical Slot: 31
	Control: I/O+ Mem+ BusMaster+ SpecCycle- MemWINV+ VGASnoop- ParErr- Stepping- SERR- FastB2B- DisINTx+
	Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-
	Latency: 0
	Interrupt: pin A routed to IRQ 11
	Region 0: Memory at c0400000 (32-bit, non-prefetchable) [size=16K]
	Capabilities: [70] Express (v2) Endpoint, MSI 00
		DevCap:	MaxPayload 256 bytes, PhantFunc 0, Latency L0s unlimited, L1 unlimited
			ExtTag- AttnBtn- AttnInd- PwrInd- RBE+ FLReset- SlotPowerLimit 0W
		DevCtl:	CorrErr- NonFatalErr- FatalErr- UnsupReq-
			RlxdOrd- ExtTag- PhantFunc- AuxPwr- NoSnoop-
			MaxPayload 128 bytes, MaxReadReq 128 bytes
		DevSta:	CorrErr- NonFatalErr- FatalErr- UnsupReq- AuxPwr- TransPend-
		LnkCap:	Port #0, Speed unknown, Width x0, ASPM not supported
			ClockPM- Surprise- LLActRep- BwNot- ASPMOptComp-
		LnkCtl:	ASPM Disabled; RCB 64 bytes, Disabled- CommClk-
			ExtSynch- ClockPM- AutWidDis- BWInt- AutBWInt-
		LnkSta:	Speed unknown, Width x0
			TrErr- Train- SlotClk- DLActive- BWMgmt- ABWMgmt-
		DevCap2: Completion Timeout: Not Supported, TimeoutDis- NROPrPrP- LTR-
			 10BitTagComp- 10BitTagReq- OBFF Not Supported, ExtFmt- EETLPPrefix-
			 EmergencyPowerReduction Not Supported, EmergencyPowerReductionInit-
			 FRS- TPHComp- ExtTPHComp-
			 AtomicOpsCap: 32bit- 64bit- 128bitCAS-
		DevCtl2: Completion Timeout: 50us to 50ms, TimeoutDis- LTR- 10BitTagReq- OBFF Disabled,
			 AtomicOpsCtl: ReqEn-
		LnkSta2: Current De-emphasis Level: -6dB, EqualizationComplete- EqualizationPhase1-
			 EqualizationPhase2- EqualizationPhase3- LinkEqualizationRequest-
			 Retimer- 2Retimers- CrosslinkRes: unsupported
	Capabilities: [b0] MSI-X: Enable+ Count=3 Masked-
		Vector table: BAR=0 offset=00002000
		PBA: BAR=0 offset=000
		
# /sys/bus/pci에서 직접 확인 : sysfs 경로에 current_link_width와 current_link_speed 파일이 있음
cat /sys/bus/pci/devices/0000\:00\:1f.0/current_link_width
0

cat /sys/bus/pci/devices/0000\:00\:1f.0/current_link_speed
Unknown

#
apt install hwinfo -y
hwinfo --pci
예시)
x8
8.0 GT/s PCIe Gen3

15: PCI 1d.0: 0108 Non-Volatile memory controller (NVM Express)
  [Created at pci.386]
  Unique ID: 1GTX.vA1igerW3PA
  SysFS ID: /devices/pci0000:00/0000:00:1d.0
  SysFS BusID: 0000:00:1d.0
  Hardware Class: storage
  Model: "Amazon.com NVMe EBS Controller"
  Vendor: pci 0x1d0f "Amazon.com, Inc."
  Device: pci 0x8061 "NVMe EBS Controller"
  SubVendor: pci 0x1d0f "Amazon.com, Inc."
  SubDevice: pci 0x8061
  Driver: "nvme"
  Driver Modules: "nvme"
  Memory Range: 0xc0408000-0xc040bfff (rw,non-prefetchable)
  IRQ: 10 (no events)
  Module Alias: "pci:v00001D0Fd00008061sv00001D0Fsd00008061bc01sc08i02"
  Config Status: cfg=new, avail=yes, need=no, active=unknown

 

 

WARP란

MinIO에서 제공하는 대표적인 S3 벤치마크 도구

# Linux x86_64
wget https://github.com/minio/warp/releases/download/v1.3.0/warp_Linux_x86_64.tar.gz
tar zxvf warp_Linux_x86_64.tar.gz
chmod +x warp
mv warp /usr/local/bin
warp --version

#
kubectl get secret -n tenant1 tenant1-tls -o jsonpath='{.data.public\.crt}' | base64 -d > tenant1.crt
kubectl get secret -n tenant1 tenant1-tls -o jsonpath='{.data.public\.crt}' | base64 -d | openssl x509 -noout -text
...
            X509v3 Subject Alternative Name:
                DNS:tenant1-pool-0-0.tenant1-hl.tenant1.svc.cluster.local, DNS:minio.tenant1.svc.cluster.local, DNS:minio.tenant1, DNS:minio.tenant1.svc, DNS:*., DNS:*.tenant1.svc.cluster.local
#
cp tenant1.crt /usr/local/share/ca-certificates/tenant1.crt
update-ca-certificates

# 
echo "127.0.0.1 minio.tenant1.svc" >> /etc/hosts

# 
mc alias set k8s-tenant1 https://minio.tenant1.svc:30002 minio minio123
mc ls --summarize --recursive k8s-tenant1
 
#
export WARP_ENDPOINT="minio.tenant1.svc:30002"
export WARP_ACCESS_KEY="minio"
export WARP_SECRET_KEY="minio123"

# 신규 터미널1 : 모니터링 disk
iostat nvme1n1 nvme2n1 nvme3n1 nvme4n1 1
iostat nvme1n1 nvme2n1 nvme3n1 nvme4n1 1 -d
iostat nvme1n1 nvme2n1 nvme3n1 nvme4n1 1 -x
iostat nvme1n1 nvme2n1 nvme3n1 nvme4n1 1 -x -d

r/s	초당 읽기 I/O 요청 수 (Read IOPS)	초당 몇 번의 읽기 요청이 발생했는지
rkB/s	읽기 KB/초	초당 읽은 데이터 양
rrqm/s	읽기 merge 요청 수	디바이스에서 merge 된 read 요청 수
%rrqm	읽기 merge 비율	merge된 read 요청의 비율
r_await	읽기 평균 대기 시간(ms)	읽기 요청이 큐에서 대기한 시간 평균
rareq-sz	평균 읽기 요청 크기(kB)	I/O 요청당 데이터 크기 평균

w/s	초당 쓰기 I/O 요청 수 (Write IOPS)	초당 쓰기 요청 건수
wkB/s	쓰기 KB/초	초당 기록한 데이터 양
wrqm/s	쓰기 merge 요청 수	디바이스에서 merge 된 write 요청 수
%wrqm	쓰기 merge 비율	merge된 write 요청 비율
w_await	쓰기 평균 대기 시간(ms)	write 요청이 큐에서 대기한 평균 시간
wareq-sz	평균 쓰기 요청 크기(kB)	I/O 요청당 write 크기 평균
...
f/s	초당 플러시 요청 수	fsync 같은 flush 호출 수
f_await	플러시 평균 대기 시간(ms)	flush 요청이 큐에서 대기한 시간
aqu-sz	평균 I/O 큐 깊이	디바이스 큐에 평균 몇 건의 요청이 쌓였는지
%util	디바이스 사용률	디바이스가 바쁜 비율(100%면 완전히 포화)

# 신규 터미널2 : 모니터링 cpu
htop

# 신규 터미널3 : 모니터링 network -> 실행 후 General interface statistics 선택
apt install iptraf-ng -y
iptraf-ng

# 신규 터미널4 : 부하 실행
# 기본 부하 테스트 실행 : mybucket 버킷에 임시 객체들을 업로드하며 성능 측정 :32개의 동시 클라이언트로 1GB 객체 100개 업로드.
# 객체 업로드 실시간 확인 해보자. 해당 버킷에 기본의 객체는 실행 종료 시 삭제됨.
warp put --host $WARP_ENDPOINT \
  --access-key $WARP_ACCESS_KEY \
  --secret-key $WARP_SECRET_KEY \
  --tls \
  --obj.size 1MiB \
  --duration 2m \
  --concurrent 32 \
  --bucket mybucket

Reqs: 21625, Errs:0, Objs:21625, Bytes: 21.12GiB
 -       PUT Average: 180 Obj/s, 180.2MiB/s; Current 187 Obj/s, 187.3MiB/s, 142.8 ms/req
Reqs: 보낸 총 요청 수 (21625건)
Errs: 에러 발생 건수 (0 → 오류 없음)
Objs: 업로드된 객체 수(=요청 수와 같음)
Bytes: 총 업로드된 데이터 용량(21.12GiB)
PUT Average: 평균 업로드 속도
  180 Obj/s → 초당 평균 180개의 객체 업로드
  180.2MiB/s → 초당 평균 180MiB 데이터 전송
Current: 지금 이 시점의 속도(실시간)
  187 Obj/s / 187.3MiB/s
142.8 ms/req: 현재 요청당 평균 지연시간(ms)


Report: PUT. Concurrency: 32. Ran: 1m57s
 * Average: 180.23 MiB/s, 180.23 obj/s
 * Reqs: Avg: 182.5ms, 50%: 154.9ms, 90%: 311.0ms, 99%: 423.4ms, Fastest: 37.0ms, Slowest: 598.0ms, StdDev: 85.6ms
PUT: PUT(업로드) 테스트
Concurrency: 32: 동시에 32개의 병렬 스레드(커넥션)로 업로드
Ran: 1m57s: 전체 테스트 1분57초 동안 실행
Average: 전체 테스트 동안 평균 전송속도
  180.23 MiB/s / 180.23 obj/s
Reqs (latency): 요청 지연 시간 통계
  Avg: 182.5ms → 요청 평균 처리시간
	50%: 154.9ms → 중간값(절반의 요청이 155ms 이내)
	90%: 311.0ms → 90%가 311ms 이내
	99%: 423.4ms → 99%가 423ms 이내
	Fastest: 37.0ms / Slowest: 598.0ms
	StdDev: 85.6ms → 응답 시간 분산 정도

Throughput, split into 117 x 1s:
 * Fastest: 296.6MiB/s, 296.65 obj/s
 * 50% Median: 176.8MiB/s, 176.78 obj/s
 * Slowest: 100.2MiB/s, 100.17 obj/s
테스트 전체를 1초 단위로 쪼개서, 각 초마다의 처리량을 계산
Fastest: 가장 높은 처리량을 보인 구간(296MiB/s)
50% Median: 중앙값 구간(176MiB/s)
Slowest: 가장 낮았던 구간(100MiB/s)



# GET 테스트 + 병렬 : 동시에 32개의 워커(thread)로 GET 요청 , 병렬 처리로 최대 throughput 측정 가능
# --autoterm 시간절약을 위해 변화가 어느정도 안정되면 자동으로 종료.
warp get --host $WARP_ENDPOINT \
  --access-key $WARP_ACCESS_KEY \
  --secret-key $WARP_SECRET_KEY \
  --tls \
  --obj.size 1MiB \
  --duration 2m \
  --concurrent 32 \
  --bucket mybucket # --autoterm

# 평균 다운로드 속도: 약 498 MiB/s, 초당 객체 수 약 498개
Reqs: 8402, Errs:0, Objs:8402, Bytes: 8402.0MiB
 -       GET Average: 499 Obj/s, 499.2MiB/s; Current 515 Obj/s, 515.2MiB/s, 65.8 ms/req, TTFB: 41.5ms
Reqs: 8402	총 GET 요청 수
Errs: 0	오류 발생 건수 (0 → 모든 요청 성공)
Objs: 8402	다운로드된 객체 수
Bytes: 8402.0MiB	총 다운로드 데이터 용량
GET Average	평균 전송 속도
 499 Obj/s	초당 객체 수
 499.2 MiB/s	초당 데이터 전송량
Current	최근 측정 시점의 실시간 속도
 515 Obj/s, 515.2MiB/s	현재 초당 객체/데이터 속도
 65.8 ms/req	요청당 평균 지연 시간
TTFB: 41.5ms	Time To First Byte, 서버에서 첫 바이트를 받기까지 평균 시간

Throughput 540202802.0MiB/s within 7.500000% for 7s. Assuming stability. Terminating benchmark.

Report: GET. Concurrency: 32. Ran: 15s
 * Average: 498.48 MiB/s, 498.48 obj/s
 * Reqs: Avg: 64.8ms, 50%: 61.2ms, 90%: 101.5ms, 99%: 146.8ms, Fastest: 4.5ms, Slowest: 262.5ms, StdDev: 28.2ms
 * TTFB: Avg: 40ms, Best: 1ms, 25th: 27ms, Median: 38ms, 75th: 50ms, 90th: 64ms, 99th: 105ms, Worst: 223ms StdDev: 20ms
Latency 통계 (요청 지연 시간)
 Avg: 64.8ms → 전체 요청 평균 지연
 50%: 61.2ms → 절반의 요청은 61.2ms 이내 처리
 90%: 101.5ms, 99%: 146.8ms → 상위 퍼센타일 지연 시간
 Fastest: 4.5ms, Slowest: 262.5ms → 가장 빠르고 느린 요청
 StdDev: 28.2ms → 지연 시간 분산

TTFB(Time To First Byte)
 평균 40ms → 서버가 첫 바이트를 응답하기까지 걸린 시간
 Best: 1ms, Worst: 223ms
 퍼센타일 통계도 제공 (25th/50th/75th/90th/99th)
 TTFB가 낮으면 서버 응답이 빠르고, 네트워크가 병목이 아님을 의미

Throughput, split into 15 x 1s:
 * Fastest: 568.4MiB/s, 568.37 obj/s
 * 50% Median: 497.1MiB/s, 497.08 obj/s
 * Slowest: 428.3MiB/s, 428.27 obj/s
테스트 전체를 1초 단위로 나누어 초당 처리량 계산
 Fastest / Median / Slowest로 속도 변동 확인 가능
 예시: 한 구간에서는 568MiB/s, 다른 구간은 428MiB/s → 변동폭 존재

 

 

 

 

 

 

 

+ Recent posts