Skip to main content Link Search Menu Expand Document (external link)

22.11.30

Docker

Layer

만약에 도커 이미지가 수정된다면 이미지를 다시 다운받아야 한다. 이 문제를 위해 도커는 레이어라는 개념을 사용한다. 유니온 파일 시스템을 이용하여 여러개의 레이어를 하나의 파일 시스템으로 사용할 수 있게 해준다. 이미지는 여러개의 읽기 전용 레이어로 구성되고 파일이 추가되거나 수정되면 새로운 레이어가 생성된다.

예를 들어, ubuntu 이미지가 A + B + C의 집합이라면 ubuntu 이미지를 베이스로 만든 nginx 이미지는 A + B + C + nginx 가 된다. webapp 이미지를 nginx 이미지 기반으로 만들었다면 A + B + C + nginx + source 레이어로 구성된다. 만약에 webapp 소스를 수정한다면 A, B, C, nginx 레이어를 제외한 레이어를 다운받으면 된다.

컨테이너는 생성될 때 기존의 이미지 레이어 위에 read-write 레이어를 추가한다. 이미지 레이어를 그대로 사용하고, 변경된 내용은 read-write 레이어에 저장되기 때문에 여러개의 컨테이너를 생성해도 최소한의 용량만을 사용한다.

Image url

이미지는 url 방식으로 관리하며 태그를 붙일 수 있다.

예를 들어, ubuntu 14.04 이미지는 docker.io/library/ubuntu:14.04 또는 docker.io/library/ubuntu:trusty 이다. 태그 기능을 잘 이용하면 테스트나 롤백도 쉽게 할 수 있다.

Dockerfile

도커는 이미지를 만들기 위해 Dockerfile 이라는 파일을 생성한다. 파일은 DSL이라는 자체 언어를 이용하고, 이미지 생성 과정을 알 수 있다. 서버에 프로그램을 설치하기 위해 의존성 패키지를 설치하고 설정 파일을 만든 과정을 Dockerfile 로 작성하면 소스와 함께 버전 관리된다.

22.11.29

Docker

도커는 컨테이너 기반의 오픈소스 가상화 플랫폼이다. 다양한 프로그램, 실행 환경을 컨테이너로 추상화하고 동일한 인터페이스를 제공하여 프로그램의 배포 및 관리를 단순하게 해준다. 어떤 프로그램이든지 컨테이너로 추상화할 수 있고, 어디에서든 실행할 수 있다.

Container

컨테이너는 격리된 공간에 프로세스가 동작하는 기술이다.

기존에는 주로 OS를 가상화했다. 이 방식은 비교적 사용법이 간단하지만 무겁고, 느려서 운영환경에서는 사용할 수 없었다. 상황을 개선하기 위해 CPU 가상화 기술을 이용하거나 반가상화 방식이 등장한다. 이 방식은 전체 OS를 가상화하지 않고 게스트 OS를 사용하여 호스트형 가상화 방식에 비해 성능이 향상된다. 그러나 반가상화도 추가적인 OS를 설치하여 가상화하기 때문에 성능문제가 있었다. 그래서 프로세스를 격리하는 방식이 등장한다.

리눅스에서는 단순히 프로세스를 격리시키기 때문에 가볍고 빠르게 동작한다. 프로세스가 필요한 만큼만 CPU나 메모리를 사용하며 성능적으로 손실이 거의 없다. 하나의 서버에 여러개의 컨테이너를 실행하여도 독립적으로 실행되기 때문에 가벼운 가상머신을 사용하는 듯하다.

Image

이미지는 컨테이너 실행에 필요한 파일과 설정값 등을 포함하고 있다.

이미지는 상태값을 가지지 않고, 변하지 않는다. 컨테이너는 이미지를 실행한 상태라고 볼 수 있다. 추가되거나 변하는 값은 컨테이너에 저장된다. 같은 이미지에서 여러개의 컨테이너를 생성할 수 있고 컨테이너의 상태가 바뀌거나 컨테이너가 삭제되더라도 이미지는 변하지 않고 그대로 남아있다.

이미지는 컨테이너를 실행하기 위한 모든 정보를 가지고 있다. 예를 들어 MySQL 이미지는 MySQL을 실행하는데 필요한 파일과 실행 명령어, 포트 정보 등을 가지고 있다. 만약에 새로운 서버가 추가된다면 미리 만들어 놓은 이미지를 다운받고 컨테이너를 생성하면 된다.

참조 : https://subicura.com/2017/01/19/docker-guide-for-beginners-1.html

22.11.25

변환 생성자의 묵시적 변환

c++에서는 변환 생성자에서 묵시적으로 형변환이 일어나는 경우가 있다. 컴파일러가 자동으로 임시 객체를 생성하여 형변환을 일으킨다. 묵시적 변환 생성자가 사용자 모르게 호출될 가능성을 차단하려면 explict 키워드를 사용하면 된다.

const 변수를 사용하다보면 non-const 변수와 함께 함수의 인자로 들어가는 경우가 있다.

참조 : https://swblossom.tistory.com/12

22.11.24

exception guarantees

  1. No-throw Guarantee 함수가 예외를 발생하지 않음을 보장하고 항상 성공적으로 수행을 마친다.

  2. Strong Guarantee 데이타베이스에서 rollback 하는 것과 같이 예외가 발생했을때, 객체의 상태가 함수 수행 전과 같이 보존되는것을 보장한다. Transactional Guarantee라고도 한다.

  3. Basic Guarantee 예외가 발생했을때 객체가 함수 수행 이전과 다른 값을 가질 수 있고, side effects 가 있을 수 있다. 하지만 오브젝트의 invariants 가 보존되고 resource leak 이 없는 상태이다.

  4. No Guarantee (exception unsafe) 이것은 exception 발생시 resource leak 을 포함하여 오브젝트의 상태에 대해서 어떤 보증도 없는 상태이다.

참조 : https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=mug896&logNo=140168091611

22.11.17

Red-Black Tree Removal

이진 검색 트리에서 2개의 자식을 가진 노드를 제거할 때, 노드 왼쪽의 최댓값이나 노드 오른쪽의 최솟값을 찾아서 제거하려는 노드의 자리로 옯긴다.
여기에서 자리로 옯긴다는 말은 단순한 값의 복사를 의미하며, 복사한 뒤에 노드는 제거된다.
값을 대체하고 제거되는 노드는 무조건 2개보다 적은 자식을 가지고 있다. 왜냐하면 이 노드는 최댓값 또는 최솟값으로 자신보다 큰 값이 없거나, 자신보다 작은 값이 없기 때문이다.
이와 같은 이유로 2개의 자식을 가진 노드의 제거도 1개의 자식을 가진 노드의 제거로 볼 수 있다. 그러므로 1개의 자식을 가진 노드를 제거하는 방법만 알면 모두 해결할 수 있다.

M을 삭제하고자 하는 노드, CM의 선택된 자식이라고 부르겠다.

  • MRED인 경우 : MC로 치환
  • MBLACK이고, CRED인 경우 : 치환하고, CBLACK으로 변경
  • MBLACK이고, CBLACK인 경우 : Double Black

Double Black 상태에서 노드 하나를 제거하면 양 쪽의 검은 노드의 갯수가 달라지기 때문에 해결할 방법이 필요하다.
MC로 치환하여 N으로 부르고, N의 형제를 S, N의 부모를 P, S의 왼쪽 자식을 SL, S의 오른쪽 자식을 SR이라고 부르겠다.
Double Black인 경우에 다음과 같은 상황이 있다.

  1. P, S, SL, SRBLACK인 경우
    • SRED로 변경
    • PN으로 정함
    • 다음 단계를 진행
  2. N이 새로운 root인 경우
    • 제거 수정 완료
  3. SRED인 경우
    • P를 왼쪽으로 회전
    • PBLACK, SRED로 색을 변경
    • N의 새로운 형제를 S로 정함
    • 다음 단계를 진행
    • SR
  4. PRED이고, S, SL, SRBLACK인 경우
    • PS의 색을 변경
  5. SBLACK이고, SLRED, SRBLACK인 경우
    • SSL의 색을 변경
    • S를 오른쪽으로 회전
    • SL을 새로운 S로 정하여 다음을 진행
  6. SBLACK이고, SRRED, NP의 왼쪽 자식인 경우
    • P를 왼쪽으로 회전
    • PS의 색을 변경
    • SRBLACK으로 변경

참조 - https://ko.wikipedia.org/wiki/%EB%A0%88%EB%93%9C-%EB%B8%94%EB%9E%99_%ED%8A%B8%EB%A6%AC

22.11.13

map 컨테이너의 메서드 중에서 insert는 삽입하려는 값이 존재하는지 먼저 확인한다. 이후에 값을 특정 위치에 삽입한다. 이 과정은 불필요하게 컨테이너의 요소를 두번 탐색한다. 한번의 탐색으로 동작이 완료되도록 최적화가 필요하다.
삽입하려는 값의 존재를 확인하는 과정에서 삽입되어야하는 위치를 얻을 수 있다. 값이 존재하지 않으면 그 위치에 새로운 요소를 연결한다. 새로운 요소를 연결하는 과정에서 포인터의 레퍼런스를 사용하면 포인터 값을 깔끔하게 정리할 수 있다.

포인터의 레퍼런스

포인터의 레퍼런스는 이중 포인터와 비슷하다.
만약에 함수 내에서 인자를 이중 포인터로 받으면 두가지를 변경할 수 있다.

  • 이중 포인터가 가리키는 포인터의 값
  • 이중 포인터가 가리키는 포인터가 가리키는 값

이중 포인터 대신에 포인터의 레퍼런스를 인자로 받는 경우에도 마찬가지로 두가지를 변경할 수 있다.

  • 포인터 레퍼런스가 가리키는 포인터의 값
  • 포인터 레퍼런스가 가리키는 포인터가 가리키는 값
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void	func(int*& x, int* y)
{
	x = y;
}

int main(void) 
{
	int   a = nullptr;
	int*  b = 10;

	func(a, &b);

	std::cout << *a << ' ' << b << std::endl;
}

/* prints
10 10
*/

다음은 이진 트리에 노드를 삽입하는 함수이다.
new_nodeparent 포인터를 변경하고, child 포인터 값을 new_node로 변경한다.
포인터 레퍼런스를 사용하였기 때문에 new_nodeparentleft인지 right인지 몰라도 된다.

1
2
3
4
5
6
7
void insert_node_at(__node_pointer parent, __node_pointer& child, __node_pointer new_node)
{
	new_node->left = nullptr;
	new_node->right = nullptr;
	new_node->parent = parent;
	child = new_node;
}

22.11.10

Tree iterator

Red-Black Tree의 반복자를 구현해본다.
tree에서 제공되는 반복자는 bidirectional iterator이므로 ++ 또는 -- 연산자로 반복자를 이동시킬 수 있다.
호출된 연산자는 tree에서 다음 node를 찾게 된다. 예를 들어, ++ 연산자는 다음과 같은 과정을 거친다.

  • 현재 위치의 node에게 right child가 있는지 확인한다.
    • right child가 있다면, 이 node에서 시작하여 가장 left에 있는 node를 찾은 후 반환한다.
    • right child가 없다면, 자신의 parent에게 자신이 left child인지 확인한다.
      • 자신이 left child이라면, parent를 반환한다.
      • 자신이 left child가 아니라면, 자신이 parent가 되어 left child인지 확인하는 과정을 반복한다.

컨테이너의 end() 멤버함수는 마지막 원소의 다음 공간을 가리키는 반복자를 반환한다.
이를 위해서 tree에 end_node를 추가해야한다. end_node는 tree의 parent이며, tree는 end_node의 left child이어야 한다.
다음과 같은 이유로 end_node의 left child는 tree이어야 한다.
연산자가 다음 node를 찾는 과정에서 child가 없다면 parent를 확인하며 올라가다가 root에 닿는다.
예를 들어, 마지막 원소를 가리키는 반복자의 ++ 연산자를 호출하면 자신이 left child일때까지 parent를 확인하며 올라간다.
결국 마지막에 root는 end_node의 left child이기 때문에 end_node를 반환하게 된다.
반대로 가장 앞의 원소에서 반복자의 -- 연산자를 호출하면 자신이 right child인지 확인하기 때문에 end_node를 반환하지 않는다.