본 포스팅은 "알면 쉬운 도커 쿠버네티스" 책을 정리한 글입니다.
구매링크 : http://www.yes24.com/Product/Goods/91618364

해야할 일은 이렇다.

  1. nginx 이미지 다운로드.
  2. nginx index.html 변경
  3. nginx 이미지 기반에 2에서 변경한 index.html 커밋해서 새로운 이미지 만들기.
  4. 컨테이너화 해보기

nginx 이미지 다운로드

terminal에서 다음과 같은 명령어를 입력한다.
docker pull nginx

그 다음 docker images 명령어로 nginx 이미지가 제대로 다운로드 됐는 지 확인해보자.

nginx 이미지로 컨테이너 만들기

terminal 에서 다음과 같은 명령어를 입력한다.
docker run --name nginx -p 8080:80 -d nginx
이와 같은 명령어를 입력하면, 컨테이너 외부 8080 포트에 컨테이너 내부(nginx이미지 기반) 80포트와 매핑 시켜준다.
docker ps 명령어로 실행중인지 확인해보고, localhost:8080으로 접근해보자


nginx의 index.html 파일 변경.

현재 localhost:8080 으로 접속해서 보이는 페이지가 nginx 의 index.html 이다.
위치를 찾아보자.
먼저 컨테이너 내부로 들어가보자.
docker exec -it nginx /bin/bash
내부로 들어왔으면 다음과 같은 명령어로 index.html 을 찾아보자
find / -name index.html 2>/dev/null
이 명령어로 index.html의 위치를 찾을 수 있다 현재 위치는 /usr/share/nginx/html/index.html이다.

이 파일을 호스트로 복사하자. 먼저 exit명령어로 컨테이너 내부에서 빠져나온 뒤 다음과 같은 명령어를 실행하자.
docker cp nginx:/usr/share/nginx/html/index.html index.html
제대로 복사됐는지 ls -al 명령어로 확인해보자.

그 다음으로는 복사된 index.html 을 수정하자.

수정한 index.html 파일 nginx 이미지 내부에 집어넣기.

아까와 비슷하다 복사 명령어로 nginx 내부에 있는 index.html 파일을 우리가 변경한 index.html 파일로 덮어써버리자.
주의할 점은 이미지가 현재 컨테이너화 되어 있어야 한다. 실행중인 상태든, 정지된 상태든 괜찮다. 일단 컨테이너화 되어있어야 한다.
docker ps -a로 확인할 수 있다.

docker cp index.html nginx:/usr/share/nginx/html/index.html
현재 이름이 nginx 인 컨테이너의 위치에 index.html을 복사하는 명령어다.
localhost:8080으로 접근했을 때 띄워져있던 페이지를 새로고침해보자. 위에서 수정한 것처럼 titleHello GraceLove 로 바뀌었다.

수정한 컨테이너를 이미지화 시키기.

하지만 도커 이미지는 불변이다. 우리가 컨테이너를 지지고 볶고 하더라도, 이미지는 바뀌지 않는다.
실험해보고싶은 분들은 docker stop nginx , docker rm nginx 명령어로 컨테이너를 지우고 다시 docker run...을 해보자.

방법 1.

이제 우리가 변경한 컨테이너를 기반으로 도커 이미지를 만들어보자.
다음과 같은 명령어를 입력한다.
docker commit nginx mynginx
nginx라는 이름을 가진 컨테이너를 mynginx라는 이름을 가진 이미지로 만드는 것이다.
다음과 같은 명령어를 입력해서 image 가 됐는 지 살펴보자.
docker images | grep mynginx

방법2.

Dockerfile을 이용하자.
다음과 같이 만들자.
vi Dockerfile (vim의 사용법을 모르겠다면, 일반적인 텍스트 에디터를 사용해도 좋다.)
아래와 같은 내용을 채워넣는다.

FROM nginx
COPY index.html /usr/share/nginx/html/

이제 빌드를 해서 이미지를 만든다.
docker build -t mynginx2 .

다음과 같은 명령어를 입력해서 image 가 됐는 지 살펴보자.
docker images | grep mynginx2

우리가 만든 이미지와 기존 nginx 이미지를 컨테이너화 시켜서 비교해보기.

현재 실행 중인 모든 컨테이너를 지우자.
docker stop [container id] -> docker rm [container id]

이제 docker ps -a 을 하면 아무것도 나타나지 않아야 한다.

깨끗한 상태에서 우리가 만든 이미지와, 기존의 nginx 이미지를 컨테이너화 시켜서 비교해보자.
docker run --name mynginx -p 8080:80 -d mynginx2
docker run --name originnginx -p 9090:80 -d nginx

그 다음 각각 localhost:8080localhost:9090 으로 접속해보자.
우리가 만든 이미지는 컨테이너화 할 때 port 8080으로 연결시켰고, 기존의 nginx 이미지는 9090에 연결시켰다.
서로 다른 index 페이지를 보여주는 것을 볼 수 있다.

개요

  • HashTable 은 Key - Value 로 이루어진 자료구조다.
  • HashTable.get(Object key)를 쓰면 해당 key와 맞는 value를 반환한다.
  • 이름에서 알 수 있듯이 HashCode를 이용한다.
  • Key를 HashFunction를 통해 정수 Hash를 만들어내고, 그 Hash를 이용해 데이터에 접근하기 때문에 속도가 매우 빠르다.( O(1) )
    • 정수 Hash를 통해 데이터에 접근해서 속도가 빠르다는데.. 그렇다면 동일한 Hash를 반환해서 동일 인덱스에 데이터가 중복될 수 있다. 이를 Hash Colliision(해시 충돌)이라 부른다. 최악의 경우 데이터 검색에 O(n) 이 걸린다. 때문에 Hash를 만들어내는 HashFunction에 쓰이는 Hash Algorithm을 잘 만들어야 한다. 입력받은 Key를 Hash로 만들 때, 최대한 잘 분배해야한다

HashTable과 HashMap의 차이는 ?
HashTable은 Thread Safe 하다.
HashMap은 Thread Unsafe 하다.
동작방식은 비슷하다.

Hash Collision이 발생한다면?

  • 해당 인덱스의 배열 저장 위치에다 바로 데이터를 집어넣는 게 아니라, LinkedList를 이용해서 차곡차곡 쌓는 방법을 쓸 수 있다.
  • 인덱스 중 비어있는 인덱스를 할당한다.

구현

key와 value를 가지고 있는 Node이너클래스 작성.

private static class Node {
    private String key;
    private String value;

    private Node(String key, String value) {
        this.key = key;
        this.value = value;
    }
}

MyHashTable을 만들고, 위에서 작성한 Node를 제네릭파라미터로 갖는 멤버 LinkedList배열 선언.

public class MyHashTable {
    LinkedList<Node>[] data;

    public MyHashTable(int size) {
        this.data = new LinkedList[size];
    }
}

HashCode를 생성해내는 getHashCode(String key) 메서드 작성

private int getHashCode(String key) {
    int hashcode = 0;
    for(char c : key.toCharArray()) {
        hashcode += c;
    }
    return hashcode;
}
  • 간단한 해쉬 알고리즘이다. key 문자열의 각각 문자를 아스키코드로 만들어 모두 더한다.
  • 맘에 들지 않는다면 Objects.hashcode(Object o) 를 쓰자

hashcode를 위에 작성한 LinkedList배열의 인덱스로 변환하는 메서드 작성

private int convertToIndex(int hashcode) {
    return hashcode % data.length;
}
  • 역시 간단한 로직이다. hashcode를 위에서 선언한 LinkedList 배열의 길이로 나머지 연산한 결과다.

입력받은 key로 LinkedList에서 Node를 찾는 searchKey 메서드 작성

private Node searchKey(LinkedList<Node> list, String key) {
    if(list == null) return null;
    for (Node node : list) {
        if (node.key.equals(key)) {
            return node;
        }
    }
    return null;
}

데이터를 집어넣는 public api인 put 메서드 작성

public void put(String key, String value) {
    int hashCode = getHashCode(key);
    int index = convertToIndex(hashCode);
    LinkedList<Node> list = data[index];
    if (list == null) {
        list = new LinkedList<Node>();
        data[index] = list;
    }

    Node node = searchKey(list, key);
    if (node == null) {
        list.addLast(new Node(key, value));
    }else {
        node.value = value;
    }
}
  1. key를 hashcode로 변환한다.
  2. (1)에서 변환한 hashcode를 index로 변환한다.
  3. 위에서 선언한 LinkedList 배열에서 index로 LinkedList를 찾는다.
    • 만약 null 이라면 새로운 LinkedList를 만들고 해당index의 배열에 할당한다.
  4. searchKey 메서드로 (3)에서 찾은 LinkedList와 key를 파라미터로 넘겨주면 Node가 나온다.
    • Node가 null 이라면 key에 해당하는 value가 없다는 뜻이므로 LinkedList의 마지막 위치에 Node를 만들어 저장한다.
    • null이 아니라면 기존에 있는 value를 바꿔치기한다.

데이터를 꺼내오는 public api인 get 메서드 작성

public String get(String key) {
    int hashCode = getHashCode(key);
    int index = convertToIndex(hashCode);
    LinkedList<Node> list = data[index];
    Node node = searchKey(list, key);
    return node == null ? null : node.value;
}
  1. key를 hashcode로 변환한다.
  2. (1)에서 변환한 hashcode를 index로 변환한다.
  3. searchKey 메서드로 해당 key를 갖고있는 Node를 찾는다.
  4. Node가 null 이라면 key에 해당하는 값이 없다는 뜻이므로 null을 반환하고, null이 아니라면 key에 해당하는 값이 있다는 뜻이므로 value를 return 한다.

전체 코드

/**
 * @author : Eunmo Hong
 * @since : 2020/06/27
 */

public class MyHashTable {

    LinkedList<Node>[] data;

    public MyHashTable(int size) {
        this.data = new LinkedList[size];
    }

    public void put(String key, String value) {
        int hashCode = getHashCode(key);
        int index = convertToIndex(hashCode);
        LinkedList<Node> list = data[index];
        if (list == null) {
            list = new LinkedList<Node>();
            data[index] = list;
        }

        Node node = searchKey(list, key);
        if (node == null) {
            list.addLast(new Node(key, value));
        }else {
            node.value = value;
        }
    }

    public String get(String key) {
        int hashCode = getHashCode(key);
        int index = convertToIndex(hashCode);
        LinkedList<Node> list = data[index];
        Node node = searchKey(list, key);
        return node == null ? null : node.value;
    }

    private int getHashCode(String key) {
        int hashcode = 0;
        for (char c : key.toCharArray()) {
            hashcode += c;
        }
        return hashcode;
    }

    private int convertToIndex(int hashCode) {
        return hashCode % data.length;
    }

    private Node searchKey(LinkedList<Node> list, String key) {
        if(list == null) return null;
        for (Node node : list) {
            if (node.key.equals(key)) {
                return node;
            }
        }
        return null;
    }



    private static class Node {
        private String key;
        private String value;

        private Node(String key, String value) {
            this.key = key;
            this.value = value;
        }
    }
}

실행

/**
 * @author : Eunmo Hong
 * @since : 2020/06/27
 */

public class HashSample {
    public static void main(String[] args) {
        MyHashTable map = new MyHashTable(3);
        map.put("hong" , "Eunmo Hong");
        map.put("grace" , "GraceLove");
        map.put("github", "https://github.com/gracelove91");
        map.put("tistory", "https://gracelove91.tistory.com");
        map.put("email", "govlmo91@gmail.com");
    }
}
  • map.put("hong" , "Eunmo Hong");
    1. 'h(104)', 'o(111)', 'n(110)', 'g(103)' 의 각각 아스키코드 값을 모두 더한다. (총합 428)
      • private int getHashCode(String key)
    2. hashCode와 위에서 선언한 LinkedList 배열의 length를 나머지 연산을 한다. (428 % 3 = 2)
      • private int convertToIndex(int hashcode)
    3. LinkedList[2]에 위치한 LinkedList를 찾아온다.
      • public void put(String key, String value) {
          ...
          LinkedList<Node> list = data[index];
          ...
        }
    4. (3)에서 찾은 LinkedList는 null이니 새로운 LinkedList를 만들어서 (3)에서 찾는 LinkedList 변수에 할당한다.
      • public void put(String key, String value) {
          ...
          if(list == null) {
           list = new LinkedList<>();
           data[index] = list;
        }
        ...
        }
    5. searchKey로 key와 (4)에서 할당한 LinkedList를 넘겨주면 Node를 반환한다.
      • public void put(String key, String value) {
        ...
          Node node = searchKey(list, key);
        ...
        }
    6. Node가 null이므로 key와 value로 새로운 Node를 만들어서 (3)에서 찾은 LinkedList의 마지막 위치에 할당해준다
      • public void put(String key, String value) {
        ...
            if (node == null) {
             list.addLast(new Node(key, value));
          }
        ...
        }

스택

  • 한 쪽 끝에서만 자료를 넣고, 뺄 수 있는 자료구조다.
  • Push로 데이터를 집어넣고, Pop으로 데이터를 빼낸다.
  • 그림 상으로는 스택의 각 인덱스에 있는 데이터를 알 수 있지만, 사실상 제일 위에 있는 데이터만 알 수 있는 자료구조다. 보통 제일 위에 있는 데이터를 top라고 한다.

스택의 구현

  • 일차원 배열 하나로 구현이 가능하다.
public class Stack<T> {
    private T[] data;
    private int size;

    @SuppressWarnings("unchecked")
    public Stack(int capacity) {
        data = (T[])new Object[capacity];
        size = 0;
    }

    public Stack() {
        this(10);
    }

    public void push(T data) {
        this.data[size++] = data;
    }

    public T pop() {
        return this.data[--size];
    }

    public int getSize() {
        return size;
    }
}
  • 1차원 배열 data와, stack의 크기를 알려주는 size를 선언했다.
  • 사용자는 스택의 최대 크기를 지정할 수 있다. 만약 최대 크기를 정해주지 않는다면 최대크기는 default 10이다
  • 앞서 말한 것처럼 push로 데이터를 저장할 수 있다.
    • 예를 들어 처음 push(10) 을 한다면 data[0]10이 저장된다.
    • 후위연산자로 식을 수행한 뒤 size + 1이 된다
  • 앞서 말한 것처럼 pop으로 데이터를 빼낼 수 있다.
    • 예를 들어 배열에 [3,1,6] 이와 같은 값이 저장돼있다면, pop()을 수행할 시 맨 마지막 값인 6을 반환한다.
    • 현재 size는 3이기 때문에 data[3]은 null이다. 따라서 전위연산자로 식을 수행하기 전 size - 1을 한 뒤 값을 빼낸다

합병정렬(merge sort)

분할정복법을 이용한 정렬이다.
분할정복법 ?
분할 : 해결하고자 하는 문제를 작은 크기의 동일한 문제들로 분할
정복 : 각각의 작은 문제를 순환적으로 해결
합병 : 작은 문제의 해를 합하여(merge) 원래문제에 대한 해를 구함.

  1. 데이터가 저장된 배열을 절반으로 나눔
  2. 각각을 순환적으로 정렬
  3. 정렬된 두 개의 배열을 합쳐 전체를 정

{'A','L','G','O','R','O','T','H','M','S'}

  1. {'A','L','G','O','R'} , {'O','T','H','M','S'}
  2. {'A','G','L','O','R'}, {'H','I','M','S','T'}
  3. {'A','G','H','I','L','M','O','R','S','T'}

{1,2,2,3,4,5,6,7}

  1. {2,4,5,7}

    1. {2,5}
      1. {2}. {5}
    2. {4,7}
      1. {4}, {7}
  2. {1,2,3,6}

    1. {1,3}

      1. {1},{3}
    2. {2,6}

      1. {2},{6}

        mergeSort(A[], p, r) -> A[p...r]을 정렬한다.
        {
        if(p < r) then 
        {
        q <- (p+r) / 2  -----> 1. p, r의 중간 지점 계산
        mergeSort(A, p, q); -----> 2. 전반부 정렬
        mergeSort(A, q+1, r); ----> 3. 후반부 정렬
        merge(A, p, q, r); ------> 4. 합병
        }
        }
        merge(A[], p, q, r)
        {
        정렬되어있는 두 배열 A[p...q]와 A[q+1...r] 을 합하여
        정렬된 하나의 배열 A[p...r]을 만든다.
        }
        void merge(int data[], int p, int q, int r) {
        int i = p; j = q+1, k=p
        int temp[data.length()];
        while( i<=q && j<=r ) {
        if(data[i]<=data[j])
         temp[k++] = data[i++];
        else
         temp[k++] = data[j++];
        }
        
while(i <= q)  
    temp[k++] = data[i++];  
while(j <= r)  
    temp[k++] = data[j++];  
for(int i = p; i <= r; i++)  
    data[i] = temp[i];  
}

+ Recent posts