Kubernetes in Action : Chapter8. 어플리케이션에서 파드 메타데이터와 그 외의 리소스에 액세스하기

    들어가기 전

    이 글은 쿠버네티스 인 액션 8장을 공부하며 작성한 글입니다.


    8.1 Downward API로 메타 데이터 전달

    이미 알고 있는 정보는 메니페스트 파일을 선언할 때 사용할 수 있다. 그렇지만 다음 정보들은 메니페스트 파일에 바로 선언해서 사용할 수 없다. 

    • 파드가 생성될 때 정해지는 메타 정보 (Node 이름, 파드 IP, 파드 이름) 
    • 파드의 Lable, Annotation

    이런 정보들을 매니패스트 파일에 제공할 수 있도록 쿠버네티스에서는 Downward API라는 기능을 제공한다. 이 기능을 이용하면 위에서 언급한 정보들을 파드에 다음 형태로 제공할 수 있다.

    • 환경변수
    • 볼륨

     


    8.1.1 사용 가능한 메타데이터 이해

    쿠버네티스가 제공하는 Downward API를 사용하면 파드의 메타 데이터를 파드 내에서 실행 중인 프로세스에 노출할 수 있다. 노출할 수 있는 정보는 다음과 같다.

    • 볼륨으로만 가능한 것
      • 파드 레이블
      • 파드 어노테이션
    • 볼륨 + 환경변수로 가능한 것
      • 파드 이름
      • 파드 IP 주소
      • 파드 네임스페이스
      • 파드가 실행 중인 노드 이름
      • 파드가 실행 중인 서비스 어카운트 이름
      • 각 컨테이너의 CPU와 메모리 요청 / 제한

    8.1.2 환경변수로 메타데이터 노출하기

    환경변수로 파드의 메타 데이터를 노출할 때는 컨테이너의 ENV 필드에 valueFrom을 사용해서 적절한 필드를 노출해주면 된다. valueFrom의 하위로 사용할 수 있는 녀석은 다음과 같다

    • fieldRef
    • resourceFieldRef

    resourceFieldRef를 이용하면, Resource와 관련된 데이터를 불러올 수 있게 된다. Resource와 관련된 데이터는 사용할 때 반드시 divisor를 붙여줘야한다. 예를 들어 아래와 같이 선언한 경우를 고려해 볼 수 있다.

    아래의 경우는 request.memory가 실제로 100Ki라는 값을 가지고 있다면, 환경변수에는 100Ki / 1Ki한 100이라는 값이 표현되게 된다. divisor를 사용해서 적절한 단위로 나누어 표현해 줄 수 있다. 

    valueFrom:
      resourceFieldRef:
        resource: request.memory
        divisor: 1Ki

    Downward API를 이용해서 파드의 메타 데이터를 환경변수로 추출하는 예제는 다음과 같다. 아래 메니페스트 파일을 이용해 파드를 생성한 후, 환경변수에 값이 있는지 확인하자. 

    apiVersion: v1
    kind: Pod
    metadata:
      name: c8-1
    spec:
      containers:
        - name: c8-1
          image: ojt90902/all:latest
          imagePullPolicy: Always
          command: ["sleep", "999d"]
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
            - name: POD_IP
              valueFrom:
                fieldRef:
                  fieldPath: status.podIP
            - name: NODE_NAME
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
            - name: SERVICE_ACCOUNT
              valueFrom:
                fieldRef:
                  fieldPath: spec.serviceAccountName
            - name: CONTAINER_CPU_REQUEST_MILLICORES
              valueFrom:
                resourceFieldRef:
                  resource: request.cpu
                  divisor: 1m
            - name: CONTAINER_MEMORY_REQUEST_KIBIBYTES
              valueFrom:
                resourceFieldRef:
                  resource: request.memory
                  divisor: 1Ki

    다음 명령어를 실행하면 파드의 메타 데이터가 설정한 것처럼 환경변수로 잘 빠졌는지를 확인할 수 있다.

    $ kubectl exec -it c8-1 -- env
    >>> 
    HOSTNAME=c8-1
    CONTAINER_MEMORY_REQUEST_KIBIBYTES=0
    POD_NAME=c8-1
    POD_NAMESPACE=default
    POD_IP=30.0.235.149

    8.1.3 Downward 볼륨에 파일로 메타 데이터 전달

    앞에서는 환경변수를 이용해서 파드의 메타 데이터를 컨테이너로 전달했다. Downward 볼륨을 이용하면 파드의 컨테이너 내부에 파일로 메타정보를 노출시켜 줄 수 있다. 사용할 때는 다음과 같은 형태로 동작한다.

    1. 볼륨을 생성하는데, 이 때 DownwardAPI 항목을 선택한다.
    2. DownwardAPI로 볼륨을 생성할 때는 다음을 작성한다.
      • path : 볼륨에서 생성될 파일의 이름. 볼륨은 정해져 있으므로, 파일의 이름만 정하면 된다.
      • fieldRef.fieldPath : 파일의 내용 
    3. Downward API로 볼륨에 저장될 파일을 정의했으면, volumeMounts를 이용해 컨테이너에 마운트 시킨다. 

    이런 형태로 작성할 수 있다. 

    apiVersion: v1
    kind: Pod
    metadata:
      name: c8-3
      labels:
        foo: bar
      annotations:
        key1: value1
        key2: |
          multi
          line
          value
    spec:
      containers:
        - name: c8-3
          image: ojt90902/all:latest
          imagePullPolicy: Always
          command: ["sleep", "999d"]
          volumeMounts:
            - mountPath: /etc/downward
              name: downward
      volumes:
        - name: downward
          downwardAPI:
            items:
              - path: "podName"
                fieldRef:
                  fieldPath: metadata.name
              - path: "podNamespace"
                fieldRef:
                  fieldPath: metadata.namespace
              - path: "labels"
                fieldRef:
                  fieldPath: metadata.labels
              - path: "annotations"
                fieldRef:
                  fieldPath: metadata.annotations
              - path: "containerCpuRequest"
                resourceFieldRef:
                  containerName: c8-3
                  resource: requests.cpu
                  divisor: 1m
              - path: "containerMemoryRequest"
                resourceFieldRef:
                  containerName: c8-3
                  resource: requests.memory
                  divisor: 1Ki

    볼륨 → 레이블과 어노테이션 업데이트

    Downward API를 이용해서 볼륨을 생성하고 생성된 파일을 확인하자. 생성된 파일은 모두 심볼릭 링크로 만들어져있는 것을 알 수 있다. 파드의 메타 데이터는 실시간으로 업데이트 될 수 있는데, 이 값들은 볼륨에 실시간으로 반영되는 것을 의미한다. 반면에 환경변수로 노출된 메타 데이터는 실시간으로 업데이트 되지 않는다. 

    $ kubectl exec -it c8-3 -- /bin/bash
    $ cd /etc/downward
    $ ls -lia
    total 4
          1 drwxrwxrwt 3 root root  200 May 28 11:45 .
    1139772 drwxr-xr-x 1 root root 4096 May 28 11:45 ..
         16 drwxr-xr-x 2 root root  160 May 28 11:45 ..2023_05_28_11_45_18.3371124528
         23 lrwxrwxrwx 1 root root   32 May 28 11:45 ..data -> ..2023_05_28_11_45_18.3371124528
         11 lrwxrwxrwx 1 root root   18 May 28 11:45 annotations -> ..data/annotations
         12 lrwxrwxrwx 1 root root   26 May 28 11:45 containerCpuRequest -> ..data/containerCpuRequest
         13 lrwxrwxrwx 1 root root   29 May 28 11:45 containerMemoryRequest -> ..data/containerMemoryRequest
         10 lrwxrwxrwx 1 root root   13 May 28 11:45 labels -> ..data/labels
         14 lrwxrwxrwx 1 root root   14 May 28 11:45 podName -> ..data/podName
         15 lrwxrwxrwx 1 root root   19 May 28 11:45 podNamespace -> ..data/podNamespace

    볼륨 스펙에서 컨테이너 수준의 메타데이터 참조

    메타 정보는 다음 수준으로 나누어진다

    • Pod 수준 : 파드 이름, 파드 IP, 파드의 노드 등등...
    • 컨테이너 수준 : 컨테이너 이름, 컨테이너에 분배된 리소스 등등

    환경변수로 노출할 때는 컨테이너의 하위 항목으로 작성되었기 때문에 그 자체가 어떤 컨테이너의 변수를 가리키는지 적절히 알고 있다.

    # 환경변수로 노출 → 컨테이너 내부에서 정의되므로, 컨테이너의 이름을 알고 있음. 
    containers:
      - name: c8-1
        image: ojt90902/all:latest
        imagePullPolicy: Always
        command: ["sleep", "999d"]
        env:
          - name: CONTAINER_CPU_REQUEST_MILLICORES
            valueFrom:
              resourceFieldRef:
                resource: request.cpu
                divisor: 1m

    볼륨을 이용해서 컨테이너 수준의 메타 데이터를 노출하는 경우를 고려해보자. 볼륨은 파드 단위에서 작성되기 때문에 명시하지 않으면 어떤 컨테이너의 메타 데이터를 가리키는지 알 수 없다. 따라서 아래와 같이 컨테이너 이름을 명시해줘야한다.

    volumes:
      - name: downward
        downwardAPI:
          items:
          - path: "containerMemoryRequest"
            resourceFieldRef:
            ## 컨테이너 이름 명시
              containerName: c8-3 
              resource: requests.memory
              divisor: 1Ki

    Downward API 사용 시기 이해

    쿠버네티스의 메타 정보가 필요한 경우, Downward API를 사용하면 어플리케이션과 쿠버네티스의 결합도가 상대적으로 낮아진다. 만약 Downward API가 없었다면, 다양한 쉘스크립트를 작성해서 정보를 얻어야 했고 이 덕분에 쿠버네티스와의 결합도가 올라갔을 것이다. 

    그렇지만 Downward API는 제한적인 메타 정보만 제공하므로 더욱 자세한 메타정보가 필요하다면 이것만으로는 부족하다. 이럴 때는 kubernetes의 API Server를 사용해야한다. 


    8.2 쿠버네티스 API 서버와 통신하기

    어플리케이션에서 클러스터에 정의된 다른 파드나 리소스에 관한 더 많은 정보가 필요할 수도 있다. 이 경우에는 Downward API만으로는 충분하지 않고, 쿠버네티스 REST APIT를 이용해야한다. 


    8.2.1 쿠버네티스 REST API 살펴보기

    먼저 쿠버네티스 클러스터의 REST API를 살펴보자. REST API를 보내기 위해서는 API Server 주소를 알아야 하는데, 아래 명령어를 이용하면 쿠버네티스 클러스터의 API 서버 주소를 알 수 있다. 쿠버네티스 클러스터 API 서버는 https로 통신으로 접속을 해야하는 것을 알 수 있다. 

    $ kubectl cluster-info
    >>>
    Kubernetes control plane is running at https://192.168.56.120:6443
    CoreDNS is running at https://192.168.56.120:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

     

     

    kubectl proxy로 API 서버 액세스하기

    API 서버로 접속하기 위해서는 https 통신을 해야한다. 인증서를 주고 받아야 하는데, 처음에 가볍게 시작하기 위해 kubectl에서 제공하는 프록시 기능으로 API 서버에 간단히 접속할 수 있다.

    // 쿠버네티스의 proxy 사용.
    $ kubectl proxy
    Starting to serve on 127.0.0.1:8001
    
    // 마스터 노드 내에서 다른 터미널 사용.
    // kube API Server와 연결된 것 확인. 
    $ curl http://localhost:8001
    {
      "paths": [
        "/.well-known/openid-configuration",
        "/api",
        "/api/v1",
        ...
    }

    위 명령어를 사용하면, 로컬 호스트의 8001 포트가 리스닝 하고 있는 것을 알고 있다. 이것은 다음과 같이 동작한다.

    • localhost:8001로 HTTP 요청을 보냄. 이 때 Proxy는 HTTP 요청을 받아들임. 
    • Proxy는 API 통신과 필요한 토큰을 알고 있기 때문에 HTTP로 통신할 수 있음.
    • client → localhost:8001 → API Server로 요청이 전송됨. 

    쿠버네티스 프록시가 열렸으면, 해당 프록시로 curl 명령어를 이용해서 REST API를 보내면 된다. 보낸 결과를 살펴보면 이런 것들을 확인해 볼 수 있다. 예를 들면 이런 내용들을 인지할 수 있다.

    • verbs : 사용할 수 있는 기능들. kubectl create, kubectl delete
    • shortnames : kubectl에서 짧게 사용할 수 있는 이름 
    # REST API 보내기
    $ curl localhost:8001/api/v1/
    >>>>
    {
      "kind": "APIResourceList",
      "groupVersion": "v1",
      "resources": [
        {
          "name": "configmaps",
          "singularName": "configmap",
          "namespaced": true,
          "kind": "ConfigMap",
          "verbs": [
            "create",
            "delete",
            "deletecollection",
            "get",
            "list",
            "patch",
            "update",
            "watch"
          ],
          "shortNames": [
            "cm"
          ],
          "storageVersionHash": "qFsyl6wFWjQ="
        },
        ...
    }

     


    8.2.2 파드 내에서 kube API Server와의 통신

    파드 내에서 kube API Server와 통신을 하려면 다음 세 가지를 처리해야한다. 이 작업들을 처리하면 파드는 쿠버네티스의 API Server와 통신할 수 있게 된다. 

    • API 서버의 위치를 찾아야 함 → IP 주소
    • API 서버와 통신하고 있는지 확인 → 인증서 이용 (certification)
    • API 서버로 인증해야함 → 토큰 이용 

    API 서버 주소찾기

    쿠버네티스는 같은 네임스페이스에 있는 서비스를 DNS를 통해 컨테이너의 환경변수로 등록해준다. 예를 들어 생성된 파드의 환경변수에는 다음과 같이 네임 스페이스에 있는 모든 서비스의 IP가 저장되어 있다. 이것을 이용해서 Kubernetes API 서버 파드를 가리키는 서비스를 만들어주면 된다. 

    $ env | grep -i service | grep -i host
    >>> 
    HELLO_NODE_PORT_SERVICE_HOST=40.0.236.128
    KUBIA_NODEPORT_SERVICE_HOST=40.0.12.103
    HELLO_SERVICE_NO_ENDPOINT_SERVICE_HOST=40.0.21.255
    CHAPTER_5_17_READNESS_SERVICE_HOST=40.0.128.217
    NGINX_SERVICE_HOST=40.0.128.149
    KUBERNETES_SERVICE_HOST=40.0.0.1
    HELLO_SERVICE_SERVICE_HOST=40.0.110.104
    
    
    $ nslookup kubernetes
    >>>
    Server:         40.0.0.10
    Address:        40.0.0.10#53
    
    Name:   kubernetes.default.svc.cluster.local
    Address: 40.0.0.1
    
    
    $ cat /etc/resolv.conf
    >>> 
    search default.svc.cluster.local svc.cluster.local cluster.local kornet
    nameserver 40.0.0.10
    options ndots:5

    이렇게 작성해주면, 파드는 API 서버로 요청을 보내기 위해서 해당 서비스에 요청을 보내기만 하면 된다. 예를 들어 다음과 같은 요청을 보내면 된다. API 서버는 요청을 받았지만 아직은 통신할 수 없다. 왜냐하면 API와 통신하는 것이 맞는지, API 서버가 요청하는 인증을 했는지 등을 증명하지 않았기 때문이다. 

    $ curl https://kubernetes
    >>>> 
    curl: (60) SSL certificate problem: unable to get local issuer certificate
    More details here: https://curl.se/docs/sslcerts.html
    
    curl failed to verify the legitimacy of the server and therefore could not
    establish a secure connection to it. To learn more about this situation and
    how to fix it, please visit the web page mentioned above.

     


    서버의 Identity 검증하기

    서버와 통신을 하기 위해서는 신뢰할만한 인증기관에서 발급받은 인증서를 이용해서 요청을 보내서, API 서버가 '진짜'인지 먼저 확인부터 해야한다. 이 과정에서 쿠버네티스가 제공하는 인증서를 이용해야한다. 이 녀석은 어디에 있을까?

    파드는 이미 API Server에서 제공하는 인증서 / 토큰을 가지고 있다. 그렇기 때문에 쿠버네티스 API 서버에 제한적으로 접속해서 필요한 일을 할 수 있는 것이다. 위치는 아래에 있다. 파드에서는 /var/run/secrets/kubernetes.io/serviceaccount 경로에 필요한 인증 정보가 들어있다. 

    $ kubectl edit pod c8-15
    >>>
    	...
        volumeMounts:
        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
          name: kube-api-access-l7jsn
          readOnly: true
          
    $ ls -lia
          1 drwxrwxrwt 3 root root  140 May 29 00:06 .
    1139343 drwxr-xr-x 3 root root 4096 May 29 00:06 ..
          2 drwxr-xr-x 2 root root  100 May 29 00:06 ..2023_05_29_00_06_19.3147573024
          6 lrwxrwxrwx 1 root root   32 May 29 00:06 ..data -> ..2023_05_29_00_06_19.3147573024
          8 lrwxrwxrwx 1 root root   13 May 29 00:06 ca.crt -> ..data/ca.crt
          9 lrwxrwxrwx 1 root root   16 May 29 00:06 namespace -> ..data/namespace
          7 lrwxrwxrwx 1 root root   12 May 29 00:06 token -> ..data/token

    여기서 ca.crt 파일(인증서)을 이용해서 API Server에 다음과 같이 인증 요청을 보내면 된다. 인증서를 이용해 API 서버가 '진짜 API 서버'인지 인증할 수 있게 된다.

    # 서버인지 확인 되었으나, 서버 측에서 인증/인가에서는 거절되어 Forbidden 발생함. 
    $ curl https://kubernetes --cacert ca.crt
    >>> 
    {
      "kind": "Status",
      "apiVersion": "v1",
      "metadata": {},
      "status": "Failure",
      "message": "forbidden: User \"system:anonymous\" cannot get path \"/\"",
      "reason": "Forbidden",
      "details": {},
      "code": 403 
    }

    만약 편리하게 인증서를 전달하고 싶다면 아래처럼 하면 된다.

    1. 환경변수를 하나 설정한다.
    2. curl https://kubernetes

    로 보내면 된다. 

    # 환경변수 추가
    $ export CURL_CA_BUNDLE=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
    
    # 요청 보낼 때, 좀 더 편함
    $ curl https://kubernetes

    API 서버에 인증하기

    이제 API 서버가 진짜 API 서버라는 것을 알았다. 그런데 아직 API 서버의 인증 절차를 통과하지 못하는 문제가 존재한다. 이 부분은 쿠버네티스 API 서버가 제공한 토큰을 이용하면 된다. 아래와 같이 조치 할 수 있다.

    # 변수로 등록
    $ TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
    
    # 요청 보내기
    $ curl -H "Authorization: Bearer $TOKEN" --cacert ca.crt https://kubernetes
    >>> 
    {
      "paths": [
        "/.well-known/openid-configuration",
        "/api",
        "/api/v1",
        "/apis",
        "/apis/",
    ...
    }

    위 방법으로 API 서버에 인증되지 않는 경우 

    쿠버네티스는 ServiceAccount에 있는 권한을 바탕으로 API 서버에 어디까지 접속할 수 있는지를 처리한다. 만약 이 토큰으로 접속 신청을 했을 때, 거절된다면 권한이 부족한 것이다. 따라서 필요한 경우에는 아래 명령어를 이용해서 일시적으로 접근할 수 있도록 바꿀 수 있다. 

    $ kubectl create clusterrolebinding permissive-binding \
    --clusterrole=cluster-admin \
    --group=system:serviceAccounts

    이렇게 하면 모든 ServiceAccount가 클러스터 관리자 권한을 가지게 되어서 접근할 수 있게 된다. 단, 보안상 좋은 방법은 아니다.


    파드가 실행중인 네임 스페이스 얻기

    인증서 / 토큰이 있는 폴더에는 네임스페이스 파일까지 함께 존재한다. 이 파일은 현재 파드가 생성된 네임 스페이스를 알려주는 파일이다. 만약 어떤 API 요청을 보내고 싶을 때, 네임 스페이스를 알아야 한다면 이 녀석을 이용해서 처리할 수 있다. 

    $ ls -l
    >>> 
    lrwxrwxrwx 1 root root 13 May 29 00:06 ca.crt -> ..data/ca.crt
    lrwxrwxrwx 1 root root 16 May 29 00:06 namespace -> ..data/namespace
    lrwxrwxrwx 1 root root 12 May 29 00:06 token -> ..data/token

    파드와 쿠버네티스가 통신하는 방법 정리

    파드의 컨테이너에서 쿠버네티스에 접근하는 방법을 정리하면 다음과 같다. 

    • 어플리케이션은 API 서버의 인증서과 인증 기관으로부터 서명됐는지 검증해야함. 인증 기관의 인증서는 ca.crt 파일에 있음. 
    • 어플리케이션은 token 파일의 내용을 Authorization HTTP 헤더에 Bearer 토큰으로 넣어 전송해 자신을 인증해야 함.
    • namespace 파일은 파드와 동일한 네임 스페이스 안에 있는 자원들에 CRUD 요청을 보낼 때 사용함. 


    8.2.3 앰베서더 컨테이너를 이용한 API 서버 통신 간소화 

    앞서 소개한 방법으로 파드에서 API 서버로 통신하는 것은 개발자가 챙겨야 할 부분이 꽤 많아진다. 또 다른 방법으로는 엠배서더 컨테이너를 도입하는 것이다. 엠배서더 컨테이너는 kubectl proxy를 이용해서 인증 / 암호화 및 서버 검증을 처리하게 한다. 

     


    엠베서더 컨테이너 패턴 소개

    실제로 구현하는 방법은 다음과 같다.

    1. 파드 안에 엠베서더 컨테이너를 하나 추가한다.
    2. 엠베서더 컨테이너는 kubectl proxy 명령어를 수행하도록 한다. 그러면 localhost:8001로 리스닝을 하기 시작한다.
    3. 동일 파드 내의 컨테이너는 localhost 네트워크 인터페이스를 함께 사용한다. 메인 컨테이너는 localhost:8001로 REST API 요청을 보낸다. 
    4. 엠베서도 컨테이너는 요청이 들어오면, kubectl proxy를 이용해 REST API 서버의 검증 / 인증 처리를 해주고 응답을 받아준다. 

    그림으로 살펴보면 다음과 같다. 


    kube proxy를 위한 엠베서더 컨테이너

    엠베서더 컨테이너는 아래 쉘 스크립트를 하나 만들어두고, 컨테이너의 ENTRYPOINT로 이 스크립트를 실행하도록 하면 된다.

    1. API Server 주소 / 인증서 / 토큰값을 변수로 지정한다.
    2. 만약 API Server 주소가 존재하지 않는다면, 동일한 네임스페이스에 kubernetes라는 이름의 서비스를 만들어주고, 해당 서비스가 마스터 노드의 API Server를 가리키도록 한다. 
    3. kubectl proxy를 호출하도록 한다. 
    #!/bin/sh
    
    API_SERVER="https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT"
    CA_CRT="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
    TOKEN="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"
    
    /kubectl proxy --server="$API_SERVER" --certificate-authority="$CA_CRT" --token="$TOKEN" --accept-paths='^.*'

    메인 컨테이너 → 엠베서더 → KubeAPI Server호출 순서

    위의 방식처럼 엠베서더 컨테이너를 만들어둔 다음, 메인 컨테이너에서 localhost:8001을 호출하면 아래 흐름으로 kube API Server가 호출된다.

     


    8.2.4 쿠버네티스 클라이언트를 사용한 API 서버와 통신

    간단한 몇 가지 작업만 수행하면 되는 경우 kubectl-proxy 엠배서더 컨테이너를 이용하면 간단한 REST API는 처리할 수 있다. 그렇지만 작업이 복잡해지는 경우라면, 언어별로 제공되는 쿠버네티스 클라이언트를 사용하는 것이 좋다.  공식적으로 지원되는 쿠버네티스 클라이언트는 아래 링크에서 확인할 수 있다.

    https://kubernetes.io/ko/docs/reference/using-api/client-libraries/


    정리

    • 단위 
      • CPU : 1(전체 코어 하나), 1m (1밀리코어)
      • Memory : 1(바이트), 1k(킬로바이트), 1Ki(키비바이트), 1M(메가바이트), 1Mi(메비 바이트)

    댓글

    Designed by JB FACTORY