Pod(파드): 여러 컨테이너를 묶어 놓은 논리적인작업 단위이다. 파드 내에는 1개의 컨테이너가 존재할 수 있고, 여러 개의 컨테이너가 존재할 수도 있다. 1개만 존재하면 싱글 컨테이너 파드, 2개 이상 존재하면 멀티 컨테이너파드라고 부른다. 그럼 컨테이너는 무엇인가? 하나의 "어플리케이션"이라고 생각하면 된다. 파드에는 여러 개의 컨테이너가 존재할 수도 있기 때문에, 웹 서버 컨테이너, 로그 수집기, 볼륨(=디스크)을 묶어 하나의 파드로 구성할 수도 있다. 

 

파드 사용하는 방법

 


파드를 사용하는 방법은 CLI(커멘드 명령의 인터페이스)로 실행시키는 방법이 있고, 아래의 그림과 같이 YAML포맷의 파일을 이용해서 생성하는 방법이 있다.

  • metdata.name: 값에 해당하는 필드 및 필드명은 이 파드의 이름을 설정하기 위함이다. 
  • spec.containers: 이 필드아래에 container을 적으면 하나씩 컨테이너가 생긴다. 아래의 예시는 컨테이너 1개를 생성하는 예시이다. "-"(하이픈)은 하위필드를 배열로 묶겠다는 것이다. 즉, 파이썬의 딕셔너리로 생각하면 {"spec": {"container": [{"image":"nginx":"latest"}, {"name":"my-nginx"}, {"resources": {}}]와 같이 속성들을 containers의 하위 필드 배열로 취급하겠다는 것이다.
  • spec.containers.image: 컨테이너에 사용될 이미지명을 적는다, ":"앞은 어플리케이션 명,  뒤는 어플리케이션의 버전정보이다. 사용자가 다운로드 받지 않아도, 이 컨테이너를 실행하는 순간에 컨테이너 허브에서 이 이미지를 다운로드(PULL)하여 가져온다.

 

Pod 생명주기


파드 생명주기는 파드 생성부터 파드의 삭제, 실패까지의 과정 중에 각각 어느단계에 있는지에 대한 개념이다. 만일 파드가 러닝중인지? 생성중인지? 실패했는지에 관한 내용을 알 수 있다. 일단 파드가 한번 생성되면 유니크안 식별자(UID, Unique ID)을 갖는다. 그리고, 삭제되기 전까지는 유지된다. 파드 생명주기 중에 어느 상태인지는 status의 필드로 확인할 수 있다.

이름이 mywebserver인 STATUS가 실행중인 파드. 어플리케이션은 1개중에 1개가 구동중

각 단계별로 의미하는 것은 아래와 같다.

상태(STATUS) 의미
Pending 쿠버네티스 클러스에서 일단 실행이 가능한 상태라고 판명된 상태. 하지만, 컨테이너중에 몇개는 아직 구동을 준비중인 상태를 의미한다. (=즉, "만들어지는 중이에요" 라는 상태)
Running 파드 내에 모든 컨테이너가 구동중인 상태
Succeeded 파드 내 모든 컨테이너가 구동에 성공된 상태. 
Failed 파드 내 모든 컨테이너가 종료된 상태. 정상 실행이 아닌 (non-zero status) 컨테이너가 존재하는 상태. 
Unknown 모종의 이유로 컨테이너 상태를 얻을 수 없는 경우

 

LivenessProbe 사용하기: 자동으로 컨테이너가 죽는 경우, 재시작 하기


kubernetes의 livnessProbe의 동작 방식 그림 (참조: https://komodor.com/learn/kubernetes-liveness-probes-a-practical-guide/)

 

  • livenessProbe을 이용해서 컨테이너 sefl-healing 이용하기

livenessProbe가 동작하는 방식은 httpget probe, tcpSocket probe, exec probe라는 것을 이용해서 각각의 목적에 맞는 일부 기능들을 주기적으로 체크를 해서 컨테이너가 정상적으로 동작 중인지를 확인한다. 예를 들어, http 웹서버라면, httpget probe은 주기적으로, 지정한 IP, port, path에 대해서 HTTP get요청을 보내서 응답이 잘 못나오는 경우 컨테이너를 다시 시작한다. tcpSocket probe은 ssh로 접속해보고 접속이 몇 번 실패하면, 해당 컨테이너를 다시 시작하는 것이다. exec probe은 명렁을 전달하고 명령의 종료코드가 0 (정상종료)가 아니면, 컨테이너를 다시 시작하는 것이다. 중요한 것은 Pod을 재시작하는 것이 아니라 Container을 재시작하는 것이다. 따라서, Pod은 그대로 이기 때문에, IP을 동일하게 사용할 수 있다.

livenessProbe을 사용하기 위해서는 몇 가지 전달해야하는 인자가 있는데, 아래와 같다. 자세한 인자들(Configuration)은 여기에서도 확인이 가능하다. initialDelaySeoncds은 컨테이너 실행 후 몇초 뒤에 probe을 실행할 것인지에 대한 속성이다. periodSeoconds은 몇초 주기로 프로브를 실행할 것지에 대한 속성, timeoutSeconds은 프로브를 실행하고나서 몇초 내에 명령들이 종료되어야하는지에 대한 속성, seccessThreshold은 이 프로브가 몇 번 연속 성공하면 성공이라고 볼것인가에 대한 것이다. failureThreshold은 프로브가 몇 번 실패해야 실패로 볼 것인지에 대한 기각역이다.

아래의 예시는 buxybox라는 이미지를 이용해서 컨테이너 1개짜리를 담은 "liveness-exam"이라는 Pod을 생성한다. 추가로, 컨테이너를 실행할 때, 컨테이너 상의 '/tmp/healthy'라는 파일을 만들고 30초 후에 삭제하는 명렁도 같이 넣어놓았다. 즉, 컨테이너가 실행되고나서 30초 동안만 파일이 존재할 것이다.

exec livenessProbe을 사용하기위한 yaml 예시. 컨테이너 실행할 때 초기에 30초동안만 파일을 생성하고, 그 뒤에 지우는 커멘트가 들어가있다. 마찬가지로 이 파일을 cat으로 확인하는 커멘드도 포함되어 있다.

 

아래와 같이 exec livenessProbe가 실행을 몇 번해보다가, failthrehold만큼 실패하면 해당 컨테이너를 재시작한다.

위의 yaml파일에서 failureThrehold가 3번이 지정되었기 때문에, 3번 검사하고 (x3 over 11s) 실패했기 때문에, 해당 컨테이너를 삭제하고 재시작하는 것이다.

 

초기화 컨테이너(Init container): 초기화 컨테이너는 파드에 앱 컨테이너가 실행되기 전에, 실행되는 특수한 컨테이너이다. 이 초기화 컨테이너로, 앱 이미지(도커이미지)에 없는 설정, 유틸리티 스크립트를 실행하여 일부 변형을 할 수 있다.


초기화 컨테이너(init container)은 Pod내에서 어플리케이션이 동작하기전에 실행되는 특별한 컨테이너를 의미한다[ref]. 주로, 만들어진 이미지를 컨테이너로 실행하면서 추가사항을 실행하기 위한 목적으로 쓰인다. 예를 들어, 구동하고자하는 앱은 RestAPI + 딥러닝추론엔진이라면, 컨테이너에는 FastAPI이미지만 있고, 초기화 컨테이너가 실행될 때, 딥러닝추론엔진만 다운로드 받아오는 식이다. 이렇게하면 딥러닝 추론엔진이 사이즈가 엄청 크더라도(>10Gb) 한 이미지에 담을 필요없이, 이미지 + 추가실행사항 정도로 이해하면된다.

일반적인 컨테이너랑 거의 유사(리소스 제한, 볼륨 사용, 보안세팅)한데, 다른 몇 가지가 있다, 대표적으로 lievnessProbe을 지원하지 않는 것이다. 그리고 초기화 컨테이너도 일반 컨테이너(regular container)와 마찬가지로 다수의 초기 컨테이너를 사용할 수 있다. 초기 세팅이 2~3개 이상 필요한 경우, 초기화 컨테이너를 2~3개 이상 명시하는 것이다. 이 때, yaml파일에 적인 순서대로 동작한다. 먼저 선언된 초기화 컨테이너가 동작하지 않으면, 뒤의 초기화 컨테이너가 동작하지 않는다. 초기화 컨테이너의 실행이 실패하면, 쿠버네티스는 실행 될 때까지 초기화 컨테이너를 구동해본다. (=쿠버네티스의 "선언적 특징"). 그리고, 초기화 컨테이너가 다 실행되어야, 메인 컨테이너를 동작한다(=초기화 컨테이너가 실행이 안되면 메인컨테이너가 구동이 안됨)

 

아래는 일반 컨테이너를 1개 구성하고 초기 컨테이너를 2개 구성한 것이다.

# myapp.yaml
apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app: myapp
spec:
  containers:
  - name: myapp-container
    image: busybox:1.28
    command: ['sh', '-c', 'echo The app is running! && sleep 3600']
  initContainers:
  - name: init-myservice
    image: busybox:1.28
    command: ['sh', '-c', "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done"]
  - name: init-mydb
    image: busybox:1.28
    command: ['sh', '-c', "until nslookup mydb.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for mydb; sleep 2; done"]

 

실제 파드를 쿠버네티스에 띄워보면 아래와 같아. myapp-pod가 초기화되려고할 때, STATUS은 Init:0/2상태로 2개의 초기화 컨테이너가 파드내에서 동작해야함을 의미하고 있다.

 

그리고, kubectl desribe pod myapp-pod을 찍어보면, 첫 번째 초기화 컨테이너(init-myservice)가 실행중임을 알 수 있다.

kubectl desribe pod myapp-pod

# std out
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  13m   default-scheduler  Successfully assigned default/myapp-pod to docker-desktop
  Normal  Pulling    13m   kubelet            Pulling image "busybox:1.28"
  Normal  Pulled     13m   kubelet            Successfully pulled image "busybox:1.28" in 5.871629222s
  Normal  Created    13m   kubelet            Created container init-myservice
  Normal  Started    13m   kubelet            Started container init-myservice

 

조금 더 자세히, 첫 번째 컨테이너의 로그를 보면(kubectl logs myapp-pod -c init-myservice) 계속 실패중이다. 

$kubectl logs myapp-pod -c init-myservice | tail

# std out
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

nslookup: can't resolve 'myservice.default.svc.cluster.local'
waiting for myservice
nslookup: can't resolve 'myservice.default.svc.cluster.local'
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

waiting for myservice

위와 마찬가지로 두 번째 컨테이너의 로그를 보면, 첫 번째 컨테이너가 실패했기 때문에, 구동을 시도조차 하지 않았을 것이다.

 

 

Infra container 사용하기


싱글 컨테이너로 파드를 구동하면 일반 컨테이너(regular)가 1개 돌아가는데, 사실은 아무일도 하지 않는 Infra container (pause) 컨테이너가 숨어있다.파드 생성될 때, 1개씩 생성된다. 파드에 대한 인프라 IP, HOSTNAME 을 관리한다. 이 파드는 식별자(UID)가 1이다. 다른 컨테이너의 부모 컨테이너로 사용(다른 컨테이너를 생성할 때, 상속받듯이 사용)이 된다. 그래서, 파드 안에 새로운 컨테이너 생성되면, Infra contatiner을 참조하여 만드니까, IP, hostname이 동일하게 생성된다. 혹시나, 이 infra conatiner가 재시작이 된다면, 파드 내에 네트웍구성이(IP, hostname)가 동일해야되기때문에, 모든 컨테이너가 다시 재시작이 된다.

 

 

스태틱 파드(Static Pod)


각 워커노드에서는 kublect 이라는 데몬이 돌아이 돌아가는데, kubelet은 파드에서 컨테이너가 확실하게 돌아가게끔 관리해주는 역할을 한다. 여태까지는 컨테이너를 띄울 때, Kubectl을 통해서, 각 노드에게 명령을 전달했다. 반면, 스태틱 파드는 직접 데몬에서 실행하는 (노드에 직접 들어가서 파드를 정의하는 디렉토리(/etc/kubernetes/manifests, kubelete configuration file)에서 직접 설정하게끔 파일을 설정하면, 자동으로 kubelet이 이 파일을 참조해서 컨테이너를 만드는 데, 이것이 스태틱파드이다. 즉, API서버 없이 특정노드에 있는 kubelet에 의해 직접 실행되는 파드를 의미한다. 쉽게 말해, 수동으로 돌리는 파드이다.

# 특정 노드로 접속
$ ssh alchemist@node1

# kubelet 설정 파일을 확인
$ docker exec -it 0a5f48991df2 /bin/bash # minikube을 docker에서 띄운 경우
$ cat /var/lib/kubelet/config.yaml  | grep staticPodPath
staticPodPath: /etc/kubernetes/manifests

# PodPath에 지금 kubelet이 실행하고 있는 자원들을 확인
$ cd /etc/kubernetes/manifests
$ ls /etc/kubernetes/manifests
etcd.yaml  kube-apiserver.yaml  kube-controller-manager.yaml  kube-scheduler.yaml

# nginx을 예시로 실행하기 
$ vi nginx-pod
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: nginx-pod
  name: nginx-pod
spec:
  containers:
  - image: nginx:latest
    name: nginx-pod
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}

# control-plane으로 들어와서
$ kubectl get pods
NAME                 READY   STATUS    RESTARTS   AGE
nginx-pod-minikube   1/1     Running   0          14s

 

Pod 생성시 컨테이너에서의 자원 제한하기(requests, limits)


Pod을 생성할 때, 각 컨테이너가 어느 노드에 실행될지는 컨트롤플래인(control-plane)의 스케쥴러가 확인해준다고 했다. 사용자가 지정으로 "특정 컨테이너를 돌릴 때, 적어도 이정도 자원이 필요해요", 또는 "특정 컨테이너를 돌릴 때, 다른 컨테이너에 영향안받게 최대 여기까지만 자원을 할당해주세요" 와 같이 요구사항/제한을 둘 수 있다. 전자(특정 컨테이너를 돌릴 때, 적어도 이정도 자원이 필요해요)는 requests, 후자(특정 컨테이너를 돌릴 때, 다른 컨테이너에 영향안받게 최대 여기까지만 자원을 할당)은 limits을 의미한다. 혹여나 컨테이너가 limits의 자원을 넘어서 할당되면, 해당 파드가 죽게되고, 다시 스케쥴링이 된다. Requests은 container을 생성할 때, 어느 최소요구사항을 만족하는 node을 찾아달라는 요청이다. 신기하게도? limits을 넣으면 requests도 같은 크기로 함께 자동으로 갖게된다.

쿠버네티스에서는 메모리의 크기는 Mi단위로 표현하고, CPU은 숫자(코어수), 또는 1000mc(밀리코어, 1CPU)로 표현한다.

 

# ngnix-pod.yaml 작성
apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod
spec:
  containers:
  - image: nginx:latest
    name: nginx-container
    ports:
    - containerPort: 80
      protocol: TCP
    resources:
      requests:
        memory: 500Mi
        cpu: 1
        
# Pod생성
kubectl create -f ngnix-pod.yaml
  
# 파드 확인
kubectl describe pod <파드명>
 Containers:
  nginx-container:
    Container ID:   docker://8cb4710e30e4ac033276b51361791de5ff9b39edfdc84ff6e315ab71f8af8d38
    Image:          nginx:latest
    Image ID:       docker-pullable://nginx@sha256:db345982a2f2a4257c6f699a499feb1d79451a1305e8022f16456ddc3ad6b94c
    Port:           80/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Tue, 19 Jul 2022 14:20:39 +0000
    Ready:          True
    Restart Count:  0
    Requests:
      cpu:        1
      memory:     500Mi
    Environment:  <none>
    Mounts:

 

 

환경변수 설정하기


Kubernetes에서 컨테이너를 실행할 때, 컨테이너에  내에서 쓰이는 환경변수를 설정할 수 있다. 아래와 같이 컨테이너(Containers)이하에 env라는 속성명 이하에 담으면 된다.언제 쓰느냐? 도커 컨테이너에서 정의된 환경변수외에 추가로 필요하거나, 변경할 때 필요하다. 

apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod
spec:
  containers:
  - image: nginx:latest
    name: nginx-container
    ports:
    - containerPort: 80
      protocol: TCP
    env:  # 환경변수의 설정
    - name: env_name
      value: "test_env_name"

 

Pod 디자인 패턴


파드 디자인패턴은 파드 내에 컨테이너를 어떻게 구성해서 실행할 것인지에 대한 설계도를 의미한다.  파드가 2개 이상의 컨테이너로 이루어져있기 때문에, 여러 파드가 있거나 의존성이 어떤지에 따라서 구성이 각가 다를 수 있다. 크게는 1) 사이드카(Side car), 2) 엠베서더(Ambassador), 3) 어뎁터(Adaptor) 패턴이 있다. 자세한 내용은 여기서도 확인할 수 있다.

1) 사이드 카 패턴: 사이드카는 아래의 그림과 같이 오토바이에 옆에 타는 부분을 의미한다. 사이드 카라는 의미는 여기서 가져왔다. 사이드카 패턴은 이미 기능적으로 잘 실행 될 수 있는 컨테이너에 뭔가 기능을 덧댄(Add on) 이미지 패턴을 의미한다. 본디 실행하고자하는 컨테이너 자체가 잘 동작하는 경우를 의미한다. 주로 로그를 쌓거나, 모니터링하는 기능을 더해서 사용하기도한다. 

Side car

2) 엠베서더(Ambassodor): 파드 외부와 내부를 연결해주는 역할을하는 것이다. 엠베서더는 대사를 의미한다. 외교사절단 대표정도로 의미가되는데, 쿠버네티스에서는 외부 TCP-Proxy로 DB을 연결해주는 역할을할수 있다. 주로 로드밸런서 역할을

3) 어뎁터 패턴(Adaptor): 어뎁터패턴은 어뎁터 용도의 컨테이너가 있어서, 각 컨테이너에서 생성하는 정보들을 모아서 관리해주거나, 또는 실제 물리적 DB에 인터페이스해서(또는 외부 데이터를 가져오는 역할), 어플리케이션에 다시 전달해주거나 해주는 역할로 사용된다. 가령 A, B, C 어플리케이션(각 컨테이너)의 로그가 제각각으로 저장되면, 이를 전처리해서 모아주는 역할을 하는 컨테이너가 있으면, 이 컨테이너가 어뎁터가 된다.

반응형

 

도커 및 Tutorius에서 제공하는 쿠버네티스를 온라인에서 실습해볼 수 있는 웹사이트

 

 

실습 환경 구성하기


 

우측의 주소로 "Play with kubernetes"을 접속한다. Play with k8s은 도커 및 Tutorius을 온라인으로 실습해볼 수 있는 웹사이트이다. 여러 인스턴스가 생성이되며, 세션은 4시간이 지나면 내부 인스턴스들이 초기화된다.

 https://labs.play-with-k8s.com/ 

 

Play with Kubernetes

Play with Kubernetes is a labs site provided by Docker and created by Tutorius. Play with Kubernetes is a playground which allows users to run K8s clusters in a matter of seconds. It gives the experience of having a free Alpine Linux Virtual Machine in bro

labs.play-with-k8s.com

 

 

kubectl ?


쿠버네티스에게 사용자가 요청하는 동작들을 전달하기 위한 명령어이다. 기본적으로, 아래와 같은 명령 구조로 이루어져있다. 각 [] (대괄호) 부분은 생략해도 되는 인자들이다.

kubectl [command] [type] [name] [flag1], [flag2]

  • [command] : 각 자원(object)에 실행할 명령. 예) ceate, get 등
  • [type]: 자원의 타입. 예) node, pod, service
  • [name]: 사용자가 만들어놓은 자원의 이름이다.
  • [flag]: 부가적으로 설정할 옵션 

type에 들어가는 오브젝트들은 아래의 내용으로 검색할 수 잇다.

 

아래와 같이 쿠버네티스의 특정 노드에 대한 정보를 "자세히" 검색해볼 수 있다.

 

 

 

 

run: 컨테이너를 1개 실행할 때,

create deployment: 컨테이너를 실행할 때 여러 파드를 실행할 때.

 

컨테이너 내부로 들어가기


$ kubectl exec (pods) 파드이름 -it -- /bin/bash

:pods은 써도 그만 안써도 그만이다. execute명령어는 pods에 한정되어서 사용되는 명령어이기 때문이다.

 

포트 포워딩하기


파드개수 조절하기

kubectl edit (오브젝트명) (이름)

 

--dry-run

 

apply 

반응형

 

핵심 요약


당사는 포트폴리오에 있는 자산중에 일부가 2024년에 최소수입보장(Minimum Revenue Guarantee, MRG)이 만기가 도래하여, 정부에서 수입을 보장해주지 않고, 초과분은 초과분대로, 손실은 손실대로 맥쿼리인프라가 갖게되어, 유입현금흐름의 변동성이 생길 수 있다 (아래 그림1). 이 글은 "2024년에 최소수입보장제도가 만기되어 실제로 현금흐름이 감소할 위험이 있는가?"에 대한 답을 알아가기 위한 내용이다. 아래의 내용은 MKIF(맥쿼리한국인프라투자융자회사)가 인프라투자로 수익을 어떤식으로 내고 있는지에 대한 내용이다. 또한, 이 수익구조에서 장래에도 안정적인 수익이 가능한지도 함께 조사한 내용이다. 결론부터 말하면, MKIF은 "융자"회사이다. 대출을 주운용수단으로한다. 

 

 

최소수입보장(MRG)에 대한 이해


최소수입보장은 민자자본으로 건설되는 도로 등 (이하 인프라)을 만들 때, 실제 수익이 예상 수익보다 못 미칠 경우 손실 일부를 보전해주는 제도이다. 일정 한도까지는 주무관청이 투자위험을 부담해주기에, 주무관청이 SOC사업에 민간의 참여를 유도할 수 있다는 장점이 있다. 97년 외환위귀로 민자투자사업이 거의 불가능하고, 부족한 인프라 건설을위해서 사업추진 일부를 주무관청이 부담하기 위한 제도이다. 일반적으로 민자사업은 30년 이상 동안 운영되므로 해당기간 동안의 시설물 이용 수요를 정확히 예측하는 것은 상당히 어렵다. 30년후에 미래의 수요를 어떻게 예상하나? 이러한 불확실성을 제거하고 민간의 참여를 적극적으로 유도하고자 정부가 도입한 것이 MRG제도다. 그러나, 민간투자사업자의 이익을 과도하게 보장하고 있다는 많은 비판이 있어, 2009년 10월에 MRG가 폐지되었다. 대신 투자위험분담방식으로 변경되었다. 

 

그림1. 정부수입보장 구조 (참조: 맥쿼리인프라)

 

 

MRG만기에 따른 유입현금흐름에 대한 이해


앞서 MRG와 MKIF의 "융자"에 관한 간단한 설명을 했다. 즉, 맥쿼리인프라의 현금흐름은 융자해준(돈을 빌려준) 회사가 디폴트가 나지 않는지, 지분을 가지고 있는 인프라회사(이하 사업시행자)가 안정적인 배당수익을 내는지가 가장 중요하다. 인프라산업의 투자는 대부분 후순위 채권이다. 엄청난 규모의 돈을 빌려야하기 때문에(한 80%정도를 빌린다), 담보가 부족한 경우가 있다. 맥쿼리인프라는 이 후순위채권에 투자한다. 즉, 투자중인 사업시행자가 "돈을 갚을 수 없어요" (디폴트)하는지 안하는지가 제일중요한데, 이를 지원해주는 것이 MRG이다. MRG가 보장된다면 고금리의 후순위 대출이자를 안정적으로 받을 수 있다. 또한, 일부 투자중인 회사는 지분을 가지고 있기도하다. 사업시행자의 지분을 갖고있어 배당수익도 받을 수 있다. 그렇기에 MRG만기가 안정적인 현금흐름을 가져갈 수 있는지에 대한 기준이된다.

투자법인(맥쿼리인프라 포트폴리오)별로 운영 및 협약기간은 아래의 그림과 같다. 총 17개 중에, 15개는 정부와 협약해서하는 "민간투자사업"이고, 2개는 평생운영할 수 있는 영속사업이다. 이 영속사업의 2개는 가스사업으로 2021년에 약 7,000억원을 유상증자하여 얻어낸 (주)해양에너지와 서라벌가스 2가지이다. 다시, 15가지의 민간투자사업으로 돌아와보면 현재 2022년에 녹색으로 표시된 "정부수입보장기간"(=MRG 기간)이 얼마안남은 자산들이 꽤 보인다는 것이다. 2024년이되면, 정부가 손실을 보전하지도 않고, 초과분을 반환하지 않아도 된다. 맥쿼리인프라가 투자한 금액을 비율대로 하였을 때, 대략 3년 정도만 손실보전을 받을 수 있을 것이라고 기대한다는 것이다(이 말이 그림 내 "민간투자사업 가중평균 재정지원 적용기간 ~3년").

당장 정부수입보장기간이 곧 종료되는 자산인 "천안-논산 고속도로", "서울-춘천 고속도로", "인천대교", "백양터널"이 정부수입보장을 지금도 받고있는지, 향후에도 받을 것인지에 대한 판단이 필요하다. 즉, 언급한 사업시행자들이 당기순이익이 (+)인지 파악하는 것이 매우 중요하다. 미래에는 MRG을 받지 못하기 때문에, 사업시행자가 디폴트고, 미수상태로 유지된다면, 유입현금흐름이 감소할 수 있기 때문이다. 

그림 2. 맥쿼리인프라의 민간투자사업 및 영속사업의 구분. 민간투자사업은 약 3년내에 MRG가 종료된다.

 

각 유입현금흐름을 보기 앞서, 각 자산들은 최소수입보장(MRG)이 어느정도 조건에 해당되어야 보존해주는지 파악해볼 필요도 있다. 2022년 3월 기준으로 백양터널은 약 3년, 천안-논산고속도로는 0.7년, 서울춘천,인천대교도 약 2.5년정도가 남았다. 이 기간내에서는 수입보장기준에 미달되면, 무주관정이 손실을 보상해주고, 수입환수기준보다 수입이 큰경우는 환수해간다. 기준은 각각 거의 90%, 110%정도이다. 눈여겨볼 것은 수입보장기준이 높을 수록 더 큰 현금흐름의 변동성을 보일 수 있다. 수입보장기준이 높으면, 현재 벌고있는 수입이 한 50%이어도, 90%을 맞춰주니까 40%정도를 주무관청(예, 도청, 국토부)이 보전해준다는 것이다. 물론, 수입량(Q)의 절대규모가 큰 자산은 10%~20%의 수입보장차이로도 꽤 큰 현금흐름유입의 변동성을 볼 수도 있다. 즉, 융자금의 미수가 발생하지 않아야한다.  잔존 재정지원기간이 있는 자산인 백양터널, 천안-논산 고속도로, 서울-춘천고속도로, 인천대교의 MRG의 기준대비 어느정도 벌고 있는지 파악이 너무나 중요하다. 

그림3. 투자자산별 주무관정 재정지원 조건 (출처: 맥쿼리인프라)

 

각 자산별 유입현금흐름은 어느정도?


맥쿼리인프라의 자산중에서 유입현금흐름은 감사보고서에서 찾을 수 있었다. 각 행에는 각 자산별, 컬럼에는 이자수익 또는 배당수익을 2021년, 2020년으로 보여주고 있다. 아래와 같이 인프라펀드는 사회기반시설을 운영하여 배당수익을 얻는과정(=지분을 투자)과 운영기관의 융자(부채), 건설기간에는 이자로 수익을 받기 때문이다(이자수익은 어찌되었든 후순위 대출로 얻는 것이다). 큰 순서대로 보면, "천안-논산고속도로" -> "부산항((주)비엔씨티)" -> "인천대교" -> "경수고속도로(용인-서울고속도로)" -> "서울-춘천고속도로" 순서이다. 만기가 도래하는 백양터널, 천안-논산고속도로, 서울-춘천고속도로, 인천대교중에서는 "천안-논산고속도로", "서울-춘천 고속도로", "인천대교"가 수익비중이 큰 순서 Top 5안에 껴있다.

그림 4.미레에셋증권 리서치센터

 

표1. 2021년의 피투자회사와의 거래내용 (출처: 맥쿼리인프라 2021년 감사보고서)

 

맥쿼리인프라의 투자포트폴리오 기준 인천대교가 가장 투자비중이크다. 천안-논산고속도로도 마찬가지로 투자비중이 높다. 특히, 천안-논산고속도로는 투자비중도 크지만, 벌어들이는 이자수익, 배당수익도 제일 크다. 

 

 

 

잔존 MRG가 있는 자산 별 MRG 보장내역


앞서 잔존 MRG가 있는 자산들이 실제로 주무관청으로부터 손실보전을 받고있는지 알아볼 필요가 있다고 했다. 이 섹션은 백양터널, 천안-논산 고속도로, 서울-춘천고속도로, 인천대교의 MRG의 기준대비 어느정도 벌고 있는지 파악한 내용이다. 

1. (주) 서울-춘천고속도로: (주) 서울-춘천고속도로는 지분 15.83%(6억), 대출 1,618억정도를 투자하고 있다 (자산 포트폴리오 규모중 8.9%).   (출처: 맥쿼리한국인프라투융자회사 공시, 2020년 12월 22일). (주) 서울-춘천고속도로의 대출금을 1,618억을 빌려주고, 아래의 표와 같이 이자 11~13.9%로 대출이자를 받고있다 (표-맥쿼리인프라 대출금). 대출금으로부터, 매해 11% 이상의 수익율 벌고 있다. 단순 계산으로만해도 1600억을 넣어서 매해 200억을 번다. (주) 서울-춘천고속도로가 파산만 하지 않는다면, 이 대출금을 매번 갚아나가고 만기가 되면 상환하면 투자자로서 안심이다. 따라서, (주) 서울-춘천고속도로가 MRG가 끝나고도 재무적으로 디폴트가 날지 안날지가 주요 포인트가 될 수 있다. 안타깝게 (주) 서울-춘천고속도로의 재무표를 찾을 수는 없었다. 하지만, 디폴트를 간접적으로 계량할 수 있는 자료(=현금흐름이 안정적인지)로 공시를 하나 발견했다. 2020년 12월 22일에 발간한 공시에 따르면, 서울-춘천고속도로 사업의 실시협약에서 추정한 통행량의 99.4%의 수준을 기록하고 있다고 한다. 따라서, MRG을 받을 것도 아니며, 어느정도의 수익을 내고 있다고 할 수 있다.

최근 공시인 2020년 12월 24일 "서울-춘천고속도로(주) 사업재구조화"에 따르면, 2020년 12월 24일부터 통행료를 28%로 감소했는데, 이로 인한 (주) 서울-춘천고속도로의 수입이 감소하는 부분(=통행료수입 차액)은 신규 대주단의 장기 대출로 전액 보전하기로한다고 공시되어있다. 쉽게 말해, 또 다른 쩐주를 구해서 쩐주돈으로 통행료수입 차액을 보전하겠다는 것이다.  그리고, 실시협약기간이 기존에 2039년까지였는데, 2054년으로 15년 연장되었다 (서울-춘천고속도로(주) 사업재구조화: 서울-춘천 고속도로 재협약내용 (2020년 12월 22일)). 어찌 되었든 투자한 회사로부터 안정적인 현금흐름을 가져갈 수 있을 것으로 기대한다. 추가로, 2018년에 8개의의 민자고속도로 감사보고서에 평균 당기순이익 218억이며, 적자인 민자고속도로는 맥쿼리가 가지고 있는 자산이 아님을 알 수 있다 (참고: https://www.womancs.co.kr/news/articleView.html?idxno=60073)

(주) 서울-춘천고속도로로 부터 얻는 이자수익
맥쿼리인프라 대출금 (출처: 맥쿼리 인프라 감사보고서, 2021년 결산내용). 11~13.9%의 이자로 표현된 것은 대출이 2회 이상으로&nbsp; 실시된 것으로 파악됨.

 

서울-춘천고속도로(주) 사업재구조화: 서울-춘천 고속도로 재협약내용 (2020년 12월 22일)

 

2.  (주) 천안-논산고속도로: 해당 사업자는 1,820억의 융자금을 가지고 있다. 1,820억중에 이자율이 6~20%로 꽤 차이 많이가 난다. 이는 05~07년까지의 이자율은 6%, 08년은 8%, 09~12년까지는 16%, 13~29까지는 20%으로 각각 따로따로 돈을 대출받아서 그렇다. 대출금 크기에 따라 가중평균해서는 11.58%정도의 이자율을 가진다 (출처: 2021년 감사보고서-4.대출금 주석 (*4)). 1,822억을 후순위 채권으로 주고, 매해 이자수익으로 239억정도를 받는다. 이것과 만만치않게 840억정도로 배당수익이 많은데, MKIF은 천안논산고속도로의 지분을 60%을 들고있다. 사실상 MKIF것이다. 다행이도 당기순이익이 2021년기준 979억정도이다(사람인 재무정보: https://www.saramin.co.kr/zf_user/company-info/view-inner-finance/csn/WkFzZXAyMUUydHdqUEtyYVhvMjBTQT09/company_nm/%EC%B2%9C%EC%95%88%EB%85%BC%EC%82%B0%EA%B3%A0%EC%86%8D%EB%8F%84%EB%A1%9C(%EC%A3%BC)).  (어찌된 건지... 당기순이익보다 초과배당한 것 같은데... 예상배당성향이 높을 수 있다)

2021년 결산 대출금 내역. (출처: 맥쿼리 인프라 감사보고서, 2021년 결산내용)
2021년 결산 이자수익 및 배당수익 (출처: 맥쿼리 인프라 감사보고서, 2021년 결산내용)

 

3. (주) 인천대교: 해당 사업시행자는 꽤 맥쿼리에게 큰 빚을 지고있다. 2,410억을 후순위로 12%의 이자를 납부하고 있다. 2017년만해도 900억정도 대출금이 있었는데, 전액조기상환했다. 그리고 다시 신규후순위대출금으로 2,410억을 대출해주었다. (주)인천대교의 지분 64.05%을 가지고있고, 금액으로는 580억원이다(시장성 없음). 원래는 한국민간인프라투자(주)가 있었고, 이 회사가 인천대교의 주식을 들고있엇던 것으로 파악된다, 이 회사의 지분을 들고있었는지 청산하면서, 재산분배로 직접취득하게되었다. 그 금액이 580억이다. 안타깝게 인천대교로부터 배당수익은 받지못하고, 이자수익을 360억정도를 받는다 ((2410억 * 12%)하면 290억정도인데.... 실제로는 모종의 이유로 360억을 받는듯..?)

 

인천대교의 부채중에서 비유동부채(만기가 37년이기에...)항목을 보면 5,460억정도가 장기차입금이다. 그 중에, MKIF대출이 2,400억정도 있을것으로 파악된다. 안타깝게도 손익계산서, 현금흐름표는 없어서... 손익계산서항목의 금융비용, 현금흐름표 투자활동의 내역을 살펴보진 못하였다. 대신 23기(21년), 22기(20년)의 재무상대표를 참고하여, 인천대교의 재무건전성을 아주 대략적이나마 볼 수 있겠다. 

인천대교 제23기 재무재표 공고 (출처: https://www.incheonbridge.com/notice_board/vread/S030/346)
인천대교 제22기 재무재표 공고 (출처: https://www.incheonbridge.com/notice_board/vread/)

 

4. 경수고속도로(=서울-용인고속도로): MKIF은 위와 마찬가지로 경수고속도로에 후순위대출을 했다. 총 규모는 996억, 이자율은 15.5%이다. 매해 지급되는 이자수익은 220억정도이다(996억 15.5%= 154억아인가.. 이것도 모종의 이유가 있는듯..). 지분은 43.75%이며, 규모로는 520억이다. 

유효이자율은 서울용인고속도로의 감사보고서를 찾을 수 있었다. 매해, 재무활동현금흐름 865억의 비용으로 나가는 것을 파악할 수 있는데, 꽤 과한 금융비용이 나가는게 아닌가 싶은데, 이중에 220억정도는 MKIF로 흘러들어가는 것으로 파악된다. 

(주)경수고속도로의 19기(21년) 현금흐름표 (출처: 경수고속도로 감사보고서(2021년).pdf)

 

 

신용관리: 혹시, 미수가 있는거 아닌가?


투자회사중에 미수가 꽤 발생하면 상당히 위험할 수 있다. 채권단에 속하는 맥쿼리인프라가 현재 현금흐름이 좋다고해도 지속적으로 미수를 줄이지못하면, 디폴트시 원금도 못받을 위험이 있기 때문이다. 100억이상의 미수이자가 발생한 투자회사들은 경수고속도로(약 110억), 인천대교(약 360억, 올해 360억을 이자로 받았어야 했는데 고스란히 못받은듯), 부산항만((주)BNCT, 400억, 미수이자가 원금을 초과했다.) 2020년에도 연체이자 492억을 을 뒤늦게 수령한적도 있고 ,만기연장 및 원금상황 일정조정으로 유동성을 틔여놓은 상태이다(2020년 베인씨티 자금재조달하이라이트).

 

맥쿼리인프라의 현재 주당 순자산가치는? (=사업가치 제외, 재무적가치)


맥쿼리인프라 2021년 결산의 감사보고서에는 자본이 2조 5,335억, 부채가 2,892억이다. 순자산만 따지면 약 2조 2443억정도이다. 사업가치는 고려하지않고, 재무상의 가치만 따지면 5,543원이된다. 현재 주가가 12,000원이니 PBR 2.0배를 넘게 받고있다. 그만큼 시장의 매력적인 회사로도 풀이해볼 수 있다.

 

본 글은 투자를 유도하는 글이 아닙니다. 이 글에는 개인 의견 있을 수 있습니다만, 투자에 대한 책임은 투자를 실행한 각자에게 있습니다.

반응형

요약


쿠버네티스는 오프젝트라는 개념으로 각 오프젝트를 정의해놓는다. 쿠버네티스내에서 사용되는 오브젝트는 가장 기본적인 구성단위를 의미한다. 즉 쿠버네티스를 돌리는 것은 이 오브젝트들의 조합으로 이루어 진다..오프젝트는 쿠버네티스에서 쓰이는 단위이다. Pod은 컨테이너의 집합이다. 네임스페이스는 논리적인 분리의 단위로, 네임스페이스 내에 각 리소스들을 할당해서 관리할 수 있다. 가령 클러스터내에 개발, 운영, 테스트의 각각의 네임스페이스를 두어, 자원을 원하는 만큼만 할당할 수 있다.

 

 

도커 데스크탑에서는 설정화면에서 쿠버네티스를 활성화하여 바로 쓸 수 있다.

 

 
 

아래와 같이 쿠버네티스 내의 어떤 오브젝트인지 확인 할 수 있다. 엄청 많지만 이를 다 외울 필요는 없고 가장 중요한 Namespace, Pod, Deployments, Replicaset 정도만 이해하고 나머지는 차근차근 이해하는 것이 권장된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ kubectl api-resources
 
NAME                              SHORTNAMES   APIVERSION                             NAMESPACED   KIND
bindings                                       v1                                     true         Binding
componentstatuses                 cs           v1                                     false        ComponentStatus
configmaps                        cm           v1                                     true         ConfigMap
endpoints                         ep           v1                                     true         Endpoints
events                            ev           v1                                     true         Event
limitranges                       limits       v1                                     true         LimitRange
namespaces                        ns           v1                                     false        Namespace
nodes                             no           v1                                     false        Node
persistentvolumeclaims            pvc          v1                                     true         PersistentVolumeClaim
persistentvolumes                 pv           v1                                     false        PersistentVolume
pods                              po           v1                                     true         Pod
podtemplates                                   v1                                     true         PodTemplate
...
cs

 

 

Namespace 란?


클러스터 내 자원을 각기 그룹 짓기 위한 논리적인 분리단위이 네임스페이스다. 군대를 다녀온 사람이면, 이해되는 비유일 수 있는데, 1중대, 2중대와 같은 개념이다. 이 1중대, 2중대가 네임스페이스면, 중대 안에 실제로 일을하는 소대(Pod) 소대 안에 여러 컨테이너(기간병)정도로 이해할 수 있다. 즉, Pod, Service, Deployment을 하나로 묶어 네임스페이스를 구성할 수 있다. 쿠버네티스가 마스터노드 외에 워커노드들이 있는데, API을 기준으로해서 이런 클러스터가 여러개 있는 것처럼 사용하기위해 쓰는 것이다.

 

 

중대 하나가 네임스페이스에 비유할 수 있다고 했다. 1중대 내에 1소대가 2개 있을 수 있을까? 없다. 1소대가 2개 있으면 소대의 구분이 어렵기 때문이다. 따라서, 네임스페이스 내에서는 독립적인 이름을 가진 오브젝트를 생성할 수는 없지만, 서로 다른 네임스페이스에서는 같은 이름을 가진 오브젝트를 생성할 수 있다. 그림으로보면 아래와 같다. 이 싱글 클러스터에서 네임스페이스에 자원을 할당할 때도, 그룹단위로 자원을 할당할 수도 있다. 네임스페이스 A는 운영용이어서 풍부한자원을 위해 CPU 100, MEM:100GB, 네임스페이스 B에는 테스트용이어서 CPU:20, MEM:16GB을 줄 수 있다. 군보급을 중대 1, 중대 2에 맞춰서 보급하는 것과 유사하다!

쿠버네티스의 싱글 클러스터는 군대의 대대급정도로 이해할 수 있다.

 

 

이제 중대에 해당하는 네임스페이스를 하나 만들어본다. 네임스페이스는 보통 YAML파일을 이용해서 다음과 같이 생성한다. 아래와 같이 vim으로 yaml파일을 작성한다. 그리고 kubectl apply 을 이용해서 생성한다. -f 옵션은 yaml파일의 경로를 직접 지정해줄 때 사용한다.

1
2
3
4
5
6
7
8
$ vim my_namespace.yaml
# 작성
apiVersion: v1
kind: Namespace
metadata:
  name: myfirst-test // "_" 불가능
 
$ kubectl apply -f my_namespace.yaml
cs

 

이렇게 만든 네임스페이스는 다음의 명령어로 조회가 가능하다. "kubectl get [오브젝트 타입]"명령어로 조회할 수 있고 pod, namespace, 등등을 쓸 수 있다. 아래와 같이 네임스페이스를 조회하여 NAME이 myfirst-test인 네임스페이스를 만들어 낼 수 있다.

1
2
3
4
5
6
7
8
$ kubectl get namespace
# 출력
NAME              STATUS   AGE
default           Active   38m
kube-node-lease   Active   38m
kube-public       Active   38m
kube-system       Active   38m
myfirst-test      Active   2m
cs

 

네임스페이스는 대대급 정도을 일컫는 논리적인 단위라고 했다. 그러면 여기에 중대나 소대에 해당하는 컨테이너를 하나 만들어볼 수 있다. 아래와 같이 리눅스 컨테이너를 하나 만들어볼 수 있다.

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Pod
metadata:
    name: myapplication_pod
    namespace: myfirst-test
spec:
    container:
    - name: my_ubuntu
      image: ubuntu:18.04
      command: "echo hello world"
cs

 

자세한 설명은 describe 명령어를 이용하여 아래와같이 쓸 수 있다.

1
2
3
4
5
6
7
8
9
$ kubectl describe namespace myfirst-test
Name:         myfirst-test
Labels:       kubernetes.io/metadata.name=myfirst-test
Annotations:  <none>
Status:       Active
 
No resource quota.
 
No LimitRange resource.
cs

 

아래와 같이 namespace에 어떤파드가 있는지 검색하면, 검색이 나오지 않는 경우가 있는데, 디폴트 네임스페이스가 "default"라는 네임스페이스이기 때문이다. 아래와 같이 네임스페이스를 따로 지정해주어야, 해당 네임스페이스내의 자원들이 검색이 된다.

$kubectl get pods
No resources found in default namespace.

$kubectl get pods --namespace="네임스페이스"

 

 

아래와 같이 네임스페이스를 삭제할 수도 있다.

1
2
3
$ kubectl delete namespace myfirst-test
# 출력
namespace "myfirst-test" deleted
cs

 

 

 POD설명


파드(Pod)는 쿠버네티스에서 생성하고 관리할 수 있는 배포 가능한 가장 작은 컴퓨터 단위이다. 파드(=고래 떼, 영어로는 Pod of whales임)라고 불리는 것은 하나 이상의 컨테이너를 포함한다. 아래의 그림처럼 각각의 고래를 도커 이미지로부터 만든 도커 컨테이너라면, 파드는 이렇게 만든 컨테이너들의 집합이 된다. 1고래 = 1컨테이너, N고래 = 파드가 된다.

 

1고래 = 1컨테이너, N고래 = 1파드의 예시. 1개의 파드에서는 N개의 컨테이너로 구성할 수 있다.

 

파드를 구성하면 무엇이 좋은가요라는 질문을 할 수 있는데, 한번에 여러컨테이너를 그룹단위로 컨트롤이 가능하다. 파드내의 컨테이너는 클러스터의 동일한 물리에서 자동으로 같은 위치에 배치되고, 함께 스케줄된다. 즉 리소스와 의존성을 고유하고, 통신할 수 있다. 즉, 파드는 기본적으로 파드에 속한 컨테이너에 네트워킹, 스토리지가 공유된다. 공식 문서에서는 "네트워크 네임스페이스를 공유한다"라고 표현하는데, 쉽게 말해 파드 내의 주소는 IP가 동일하다는 것이다. 따라서, localhost로 서로 통신이 가능하다. 또한, 같은 파드내에서는 모든 컨테이너들이 공유 볼륨을 갖는다. 파드 A안에 컨테이너1과 컨테이너2가 서로 같은 물리적인 공간에 있기 때문에, 같은 디스크 공간에 접근해서 사용할 수 있다. 예를 들어, 컨테이너 1이 작성한 txt파일을 컨테이너2가 쓸 수 있는 것 이다 (=볼륨이 동일하다라고 표현한다).  

 

아래와 같이 파드내에 'ngnix'을 실행하는 컨테이너를 띄울 수 있다. apiVersion은 단순히 V1이라는 것이고, 유심히 봐야할 것은 kind: Pod인 것이다. metdata내에는 name: nginx라는 이름으로 pod을 띄운다는 것이며, spec내의 내용들이 컨테이너로 띄워진다. 아래와 같이 작성한 pod의; yaml파일이 있으면 kubectl apply -f <pod을 작성한 yaml파일주소>로 실행하면된다.

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.14.2
    ports:
    - containerPort: 80
cs

 

 

반응형

 

요약


Quadruplet loss은 Triplet loss에서 같은 클레스끼리 더 응집할 수 있도록 제약을 추가한 손실함수이다. Quadruplet loss함수를 기반으로 딥러닝 네트워크를 만든 것이 Quadruplet loss이다. 이 Quadruplet loss을 이용하면 같은 클레스(Intra-class)는 더 응집력 있게 임베딩이 되며, 다른 클레스(Inter-class)끼리는 더 거리가 멀어지게 임베딩할 수 있게 학습을 한다. 또한 Quadruplet network에서는 학습을 더 잘할 수 있게, 이미 구별이 어려운 데이터포인터들을 먼저 학습할 수 있게하는 전략도 함께 구사한다(=어려운 문제를 잘 풀다보면 쉬운 문제를 잘 푸는 것과 같은 이치)

 

 

 

Quadruplet loss


Quadruplet loss의 핵심은 아래의 손실함수의 정의이다 (식3).  $x_{i}$의 의미는 Anchor에 해당하는 데이터 포인터이다. (즉 쿼리에 해당하는 데이터포인터임). $x_{j}$은 $x_{i}$의 클레스와 같은 클레스를 지닌 데이터포인터이다(=Positive sample이라고도 한다). 한편, $x_{k}$은 $x_{i}$와 다른 클레스를 지닌 데이터포인터이다. 여기까지가 Triplet loss의 정의를 위한 과정인데, Quadruplet은 $x_{l}$이라는 다른 데이터포인터의 고려사항을 두는데, $x_{k}$랑은 다른 클레스의 negative sample을 의미한다. 식 3에서의 마지막 s들은 class을 의미한다.

 

 

식(3)에서의 g(x1, x2)은 무엇인가? g()함수는 "learned metric"이라고 영어로 표현한다. 쉽게 말하면, 딥러닝 네트워크에서 뱉어주는 유사도에 해당한다. learned metric에서 여기서 쓰는 g(x1, x2)은 두 이미지(x1, x2)사이의 유사도를 의미한다. g(x1, x2)가 크면 클수록 이미지가 다른 것을 의미하며, 유사한 이미지일수록 같은 클레스의 이미지일 것이라고 학습시킨 것이다.

 

다시 돌아와서, 식(3)을 그림으로 표현하면 다음과 같다. 식(3)에서의 첫 번째 term은 아래의 그림의 좌측에 해당하고, 두 번째 term은 아래의 그림의 우측에 해당한다. 좌측그림을 보면, $x_{i}, x_{j}$은 같은 클레스이다. 따라서 두 클레스의 유클리디언 공간에서의 위치는 유사해야하기에, 두 거리에 해당하는 g(x1, x2)은 가까워야한다(=0에 수렴). 반대로 $x_{k}$은 고양이 이다. 따라서, $x_{i}, x_{k}$은 멀어져야 의미론적으로 임베딩이 되었다고 할 수 있다. 즉, 첫 번째 Term은 같은 클레스의 그림은 가깝게, 다른 클레스의 그림은 멀게 표현하라는 제약을 둔 것이다. 

반대로, 우측의 이미지를 첫항에 해당하는 $x_{i}, x_{j}$은 같은 클레스이다. 따라서 두 클레스의 유클리디언 공간에서의 위치는 유사해야하기에, 두 거리에 해당하는 g(x1, x2)은 가까워야한다(=0에 수렴). 그 다음항은 $x_{l}, x_{k}$이다. $l$은고래이다.  $k$와도 다르고 $x_{i}$와도 다르다. 즉, negative samples의 클레스 사이끼리도 거리가 멀어야한다는 것을 의미한다.

 

Quadruplet loss의 그림. 각 손실함수의 항별 의미를 그림으로 표현했다.

 

Quadruplet network


그리고 나서, 아래의 그림과 같이, 모델을 구성하여 학습한다. 아래의 그림중, 흰색배경에 해당하는 부분은 Triplet network (withtriplet loss)으로 구성과 동일한 부분이다. 빨간색 쉐딩이 되어있는 부분은 본 연구에서 추가한 항목이다. 좌측의 입력(Quadruplet)부터보면 Ref와 Pos은 같은 이미지이다. Neg은 흰색옷을 입은 Ref와 다른 이미지이다. 그리고, 마지막 Neg2은 Neg1과도 다르고, Ref와도 다르다. 그리고, 뉴럴네트워크를 이미지 4개를 한번에 입력값으로 받는다.이 뉴럴네트워크는 (Shared)라고 표기한 것처럼, 가중치를 공유한다. 즉, 단일 네트워크를 각각 학습시키는 것이 아닌, 동시에 같은 네트워크를 학습시키는 것이다. 그 후, g(x1, x2)에 해당하는 FC8을 통과하여 이미지의 유사도에 해당하는 벡터를 반한다. (주의할 것은 triplet에서 적용한 g(x1, x2)은 상수를 반환하고, 본 연구에서 반환하는 것은 2차원 vector을 반환한다). 벡터를 반환시, 벡터의 첫원소는 두 이지의 유사도를 의미할 것이라고 가정하여 softmax을 취한 normalization값을 얻는다(=0과 1사이). 그 후에 quadruplet loss을 적용한다.

 

 

Margin-based online hard negative mining


문제는 이 이미지 4개를 어떤 조합으로 넣을지, 어떤 순서로 넣을지이다. 본 연구에서는 Margin-based online hard negative mining이라는 방법으로 학습방법을 정의했다. 직관적으로 해석하면, 구분하기 쉬운문제-> 구분하기 어려운문제로 학습하는 것보다, 애초에 구분하기 어려운문제부터 학습하는 것이 더 좋을 것이라는 직관에 해당한다. 또한, 얼마나 멀어야 구분하기 어려운지(margin)을 정의하기는 어렵다. 여기서는 다음의 방법을 이용했다.

마진은 $\alpha$로 표현하는데, Anchor와 negative class와의 learn metric(=g(x1, x2), 유사도)의 평균과 Anchor와 positive class의 learned matrix의 차이를 이용한다 (식 4).  아래의 각 $\mu_{n}, \mu_{p}$은  Anchor와 negative class와의 learn metric의 평균, Anchor와 positive class의 learned matrix을 의미한다. 여기서의 $x_{l}$은 등장하지 않는다. 본문에는 등장하지 않지만, $l$보다는 $i, j, k$와의 마진을 구현하기 위하기 위함일 것이다.  

실제로 이렇게 구하는 $alpha$은 각각 식(3)에 다시 할당이되어야 한다.문제는 식(3)에서 에서 $alpha_{1}, alpha_{2}$을 각각 구하기위해서는 식4와 같이 N까지의 모든 샘플을 구해야한다는 것인데, 각 iteraion마다 g(x1, x2)의 파라미터가 바뀌니, $alpha$도 각 이터레이션마다 업데이트해주어야한다. 즉, 모든 샘플에 대해서 1회 이터레이션당 $\mu_{n}, \mu_{p}$을 계산해야한다는 것이다. 본 연구에서는이를 간단하게 해결하기위해서, N개의 이미지쌍을 구현하는 것이 아니라, 배치사이즈 M을 이용하기로했다. 학습할 때, 1회업데이트에 쓰이는 배치사이즈(M)에 대해서만 계산한다는 것이다. 즉, $N_{p}=M, N_{n}=2M$이 된다. 

또한, 파라미터의 최적화를 위해서, 스토케스틱 그레디언트 디센트(SGD)을 계산에서도 $\alpha$가 껴있기 때문에 계산이 어려울 수 있으니 아래의 식을 이용한다.

반응형



요약


Docker을 이해하기위해서는 아래의 도커관련된 개념들의 이해가 필요하다.



* 도커이미지?
도커 이미지는 도커에서 필요한 서버 프로그램 + 소스코드 + 실행 파일로, 프로그램을 구동하기위한 종합선물세트이다.
추가적인 설치등이 필요없게 하나의 포장으로 엮어놓은 패키징된 파일을 의미한다.
예를 들어, 스타크레프트 2를 베틀넷클라이언트로 설치할 수 있지만, 윈도우 + 베틀넷클라이언트로 이미 깔린 코드 + 그레픽카드 드라이버 등을 한 번에 포장해서 도커이미지로 만들어놓으면,
똑같은 시스템을 가진 사람이 도커이미지로



* 도커 컨테이너(Docker container) ?
이미지를 실행한 상태를 의미. 패키징된 도커이미지를 격리된 동간에서 동작시키는 것(기술)을 의미한다.


* 이미지(Image)와 레이어(layer)?

 

Docker 설치


다음의 URL에서 "Docker Desktop for Windows"을 클릭. (URL: https://docs.docker.com/desktop/windows/install/)

Click Click만 하면 된다. 필요하다면 WSL을 설치해야한다는데, 다음의 과정을 진행하면된다.

1. 관리자권한으로 Powershell.exe을 켠다.

2. 아래의 코드를 하나하나 붙여넣는다. 중간에 "https"으로 시작하는 주소의 wsl도 설치가 필요하다.

dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart

# https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi < 주소 복사해서 설치한다.

wsl --set-default-version 2

 

3. 아래와 같이 Docker Desktop이 설치가 가능하다.

 

 

 

Docker로 linux container 실행하기


처음에 docker을 설치하고, docker에 있는 이미지를 아래와 같이 검색할 수 있다. 하지만 설치되어있는 이미지가 하나도 없기에 출력결과는 아무것도 없다.

docker images
# 아무것도 출력이 안됨

 

 

다음과 같이, Docker Hub에서 이미지를 검색할 수 있다. 이 공간(Docker Hub)은 공개된 공간으로 여러 이미지들이 공유되고, 개발자들이 다운로드 받을 수 있다. 

docker search ubuntu

 

 

아래와 같이, Docker Hub에서 "ubuntu"이미지를 검색해보자. 아래와 같이 여러 출력결과 들이 나오는데, 별(STARS, 일종의 "좋아요")이 가장 많고, OFFICIAL인 이미지를 하나 다운로드(PULL)해서 설치해본다.

# docker pull [옵션] [경로]<이미지명>[:테그명]
docker pull ubuntu

 

 

Docker 내에 제대로 이미지가 다운로드(PULL)되었는지 확인해보자. Docker 이미지가 검색이 되어야하므로 'docker image'라는 명령어로 설치된 이미지를 검색한다. 아래와 같이 1개의 리포지토리명이 ubuntu인 이미지가 설치된다.

docker images

# 출력
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
ubuntu       latest    27941809078c   2 weeks ago   77.8MB

 

 

이제 도커이미지는 빌드되어있는 것을 컨테이너로 띄우기만하면 된다. 도커 컨테이너에서 아래와 같은 명령어로 실행시킬 수 있다. 아래의 명령어의 해석은 다음과 같다.

  • -d 옵션: 이미지로부터 도커 컨테이너를 띄울 때, 백그라운드에서 돌 수 있도록 실행하여라. 이는 예를 들어, youtube뮤직을 틀어 놓고 다른작업을 하기위한 것과 동일하다. 흔히 이것을 detach모드로 실행시킨다고도 한다.
  • --name <container 명>: 도커 컨테이너를 실행할 때, 지정할 이름을 넣는다. 아래의 예시는 "my_ubuntu"로 실행하였다.
  • -i : 상호 입출력
  • -t : bash 쉘을 사용할수있도록함
docker run -dit --name my_ubuntu ubuntu

# 출력
74a92f6f04354520d3709d370bf1dabbc12aaf432225001eb6a2a96feaaa7d12

 

 

우분투 컨테이너가 제대로 설치되어있는지 다음의 명령어로 확인해보자.

docker ps

# 출력
CONTAINER ID   IMAGE     COMMAND   CREATED          STATUS          PORTS     NAMES
74a92f6f0435   ubuntu    "bash"    14 seconds ago   Up 13 seconds             my_ubuntu

 

 

컨테이너에 아래와 같이 접속이 가능하다.

# CONTAINER ID을 이용한 접속
docker attach 74a92f6f0435

# CONTAINER NAME을 이용한 접속
docker attach my_ubuntu

 

 

컨테이너에서 도커 컨테이너 종료없이 나오고 싶을 때는 키보드에서 아래의 자판을 연달아 누른다. 철권에서 케릭터 커멘드 누르듯 연달아서 다음을 눌러준다. "CTRL+P"->"CTRL + Q" (=deteching without stopping).

# 키보드에서 CTRL+P -> CTRL+Q
root@d70fbbd0ee4b:/# read escape sequence

 

 

혹시, 도커가 사용이 중지되어서 다시 시작하고 싶다면, docker start을 하면된다. 

# 도커가 중지된 경우 docker ps -a로 확인가능 
# -a option: 프로세스 중인 모든 컨테이너(all)을 확인
docker ps -a
CONTAINER ID   IMAGE     COMMAND   CREATED       STATUS                           PORTS     NAMES
d70fbbd0ee4b   ubuntu    "bash"    2 hours ago   Exited (137) About an hour ago             my_ubuntu


# 도커 시작 (ID로)
docker start d70fbbd0ee4b  # 또는

# 도커 시작 (name으로)
docker start my_ubuntu

 

 

 

도커이미지 빌딩을 위한 Dockerfile 작성법


 

 

도커 컨테이너는 도커 이미지가 실제 구동되는 것을 의미하고, 하나의 도커 이미지로 어려 도커컨테이너를 실행시킬 수 있다. 비유하자면, 도커 컨테이너가 밀키트 만드는 제조법으로 끓여낸 부대찌게라면, 도커이미지는 밀키트 제조 레시피 및 재료이다. 위에서는 도커이미지를 도커허브(Docker Hub)에서 다운로드받아서 사용했고, 이를 이용해서 도커 컨테이너를 구동하였다. 그렇다면, 도커이미지를 만들기 위해서는 어떤 것들이 필요한가? 이 정답은  Dockerfile이다. Dockerfile은 도커이미지를 만들기 위한 레시피를 의미한다. 이 레시피에는 어떤 파일들을 참조하고있는지, 어떤 과정이 진행되는지 파악할 수 있다.

 

 

 

 

Dockerfile 에서 사용되는 명령어의 모음

아래와 같이 도커파일로부터 이미지를 만들 때 쓸 수 있는 명령어는 다음과 같다. 주의할 것은 RUN은 이미지를 만들 때 실행되는 코드이며, ENTRYPOINT, CMD은 컨테이너를 만들고나서 실행되는 코드이다.

 

명령어 기능 예시
FROM 새로운 이미지를 생성할 때, 기반으로 상용할 이미지를 지정. 주로 OS을 지정한다. 버전까지 지정해주는 것이 좋다. (어짜피 버전까지 지정해서 패키징해야, 플렛폼 독립적으로 쓸 거니깐).

Dockerfile은 반드시 FROM 구문으로 시작해야한다. 이미지가 내 로컬 docker 이미지가 없어도 손쉽게 퍼블릭 리포지토리에서 가져올 수 있다.
FROM ubuntu:18.04

ENV
환경변수를 할당하고 싶을 때 사용 ENV PROJECT_DIR MY_DIR

: 도커이미지을 만들 때, PROJECT_DIR 환경변수에는 MY_DIR을 할당하라는 명령어
WORKDIR shell의 cd 명령문처럼 컨테이너 상에서 작업 디렉토리로 전환을 위해 사용됨  
COPY . 호스트 컴퓨터에 있는 디렉터리나 파일을 Docker이미지 내부로 복사하기위해 사용된다. 도커 컨테이너는 격리된 공간에서 패키징된 이미지를 실행한다고 했기에 별도의 호스트컴퓨터(진짜 물리적인 컴퓨터)내에 있는 자료를
가져오기위해서는 별도의 명령어가 필요하다. 이 명령어가 COPY 명령어이다
COPY ~/model ./model
RUN Dockerfile로부터 도커이미지를 빌드하는 순간에 실행된다. 
* RUN명령어는 실행시마다 레이어가 생생되고 케시된다.

* RUN은 이미지를 만들 때 생성되는 명령어이다.
RUN apt-get update
ENTRYPOINT ENTRYPOINT은 컨테이너 생성 후, 최초로 실행되는 명령어이다.  
CMD CMD도 컨테이너 생성후에 실행되는 명령어이다 (실행이 안될 수도 있다).

docker run <image-name> [CMD]의 명령어에서 CMD가 없는 경우에 디폴트로 돌리기 위함이거나, 인자를 받기위함으로 사용된다.

docker run 뒤에 CMD가 없으면 default로 실행, 다른명령어가 있으면 실행이 안됨
 

 

 

FROM 명령어: 아래와 같이 dockerfile을 하나 작성하고, ubuntu:18.04을 작성한다. 왠만하면 버전명까지 넣어준다. docker build -t [이미지명]으로 도커이미지를 빌딩한다.

# vim을 이용하여 dockerfile을 작성
vim dockerfile


# vim 내 내용
FROM ubuntu:18.04

# vim을 빠져나옴
$ docker build -t my_ubuntu:first .

# 출력
[+] Building 1.3s (5/5) FINISHED
 => [internal] load build definition from Dockerfile                                                               0.0s
 => => transferring dockerfile: 31B                                                                                0.0s
 => [internal] load .dockerignore                                                                                  0.0s
 => => transferring context: 2B                                                                                    0.0s
 => [internal] load metadata for docker.io/library/ubuntu:18.04                                                    1.2s
 => CACHED [1/1] FROM docker.io/library/ubuntu:18.04@sha256:478caf1bec1afd54a58435ec681c8755883b7eb843a8630091890  0.0s
 => exporting to image                                                                                             0.0s
 => => exporting layers                                                                                            0.0s
 => => writing image sha256:d513fc32a2db453cb336aae40870e42a81451570aadbdc25e1662fb07dcfb1b0

 

 

도커 이미지 빌딩 결과는 다음과 같다. 아래와같이 도커리포지토리 이름은 my_ubuntu이며 ":first"으로 적어주었던부분은 TAG란에 들어갔다. 실제로는 TAG란에는 주로 버전명을 기입해서 소프트웨어의 버전관리를 용이하게 할 수 있다.

$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
ubuntu       latest    27941809078c   2 weeks ago   77.8MB
my_ubuntu    first     d513fc32a2db   2 weeks ago   63.1MB

 

 

도커 RUN은 연속해서 실행할 때 마다 새로운 레이어가 생성이 된다. 예를 들어 RUN을 두번 실행되면, FROM으로 생성된 이미지 -> 컨테이너로 생성 -> 첫 번째 RUN을 실행하여 얻은 이미지 -> 첫 번째 RUN을 실행하여 얻은 이미지에서 컨테이너를 만듬 -> 이 컨테이너에서 두 번째 RUN을 실행 -> 이 내용을 이미지로 저장. 아래와같이 dockerfile을 하나 생성하고 이미지 빌딩과정을 하나 추적해보자.

 

 

 

 

 



이제 dockerfile을 하나 작성하여 docker image을 하나 생성해보자. 아래의 dockfile을 이용해서 빌딩된 도커이미지의 로그를 아래와 같이 이해할 수 있다.먼저 FROM 에 적인 ubuntu:18.04가 docker이미지에 없기에 docker.io에서부터 다운로드받아온다. 그다음에 캐시를 해놓는다.캐시해놓은 이미지로 다시 컨테이너를 띄우고, 그 후 RUN apt-get-y udpate을 한다. 그 후에 다시 이미지 캐시하고 다시 컨테이너를 띄우고 마지막 RUN apt-get -y install curl을 실행한다. 마지막에 RUN apt-get -y install curl까지 완료되면 해당 이미지를 내뱉는다. 

# dockerfile
FROM ubuntu:18.04
RUN apt-get -y update
RUN apt-get -y install curl

# docker image build
docker build -t my_ubuntu:v1.0 .

# 출력
 docker build -t my_ubuntu:v1.0 .
[+] Building 49.9s (7/7) FINISHED
 => [internal] load build definition from Dockerfile                                                               0.0s
 => => transferring dockerfile: 108B                                                                               0.0s
 => [internal] load .dockerignore                                                                                  0.0s
 => => transferring context: 2B                                                                                    0.0s
 => [internal] load metadata for docker.io/library/ubuntu:18.04                                                    1.5s
 => CACHED [1/3] FROM docker.io/library/ubuntu:18.04@sha256:478caf1bec1afd54a58435ec681c8755883b7eb843a8630091890  0.0s
 => [2/3] RUN apt-get -y update                                                                                   21.1s
 => [3/3] RUN apt-get -y install curl                                                                             26.9s
 => exporting to image                                                                                             0.2s
 => => exporting layers                                                                                            0.2s
 => => writing image sha256:a6d72fef1af26555d21b78e4ca5e985bfa82df9dfcd6c8d37dd69d0178e93a32



아래와 같이 docker buil을 실행하면 처음에 Ubuntu 18.04을 베이스로하는 Container을 띄우고, 그 위에 layer 1. layer 2을 쌓아 최종 image을 만든다. 그렇게 만든 이미지는 추가적으로 변경할 수 없고 READ ONLY상태이다. 이를 container로 비로서 띄워야만 컨테이너 내부에서 파일도 쓰고 읽고 할 수 있다. 

 

 

Docker layer의 개념 도식화




생성된 도커 이미지는 아래와 같이 삭제할 수 있다.

$ docker images
# 출력
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
my_ubuntu    v1.0      a6d72fef1af2   9 minutes ago   119MB
ubuntu       latest    27941809078c   2 weeks ago     77.8MB


$ docker rmi a6d72fef1af2
# 출력
Untagged: my_ubuntu:v1.0
Deleted: sha256:a6d72fef1af26555d21b78e4ca5e985bfa82df9dfcd6c8d37dd69d0178e93a32

 

 

 

반응형

+ Recent posts