개요

Jenkins Container 환경 설정 시 웹 페이지의 경로를 변경하고, nginx 프록시 할 수 있도록 구성하는 방법을 알아보자.

Host OS 내에 jenkins container를 이미 구성한 상태에서 접속할 수 있는 경로만 변경하는 작업을 포스팅 한다.

환경

필자의 경우 Host OS(Ubuntu Linux) 위에 Jenkins Container를 구성했고, Host OS에 Nginx를 설치하여 리버스 프록시를 활용하여 Jenkins 관리 웹 페이지로 이동시키게끔 구성했다.

Jenkins Container 설정

StackOverflow의 글에서 경로를 변경하기 위해서 환경변수를 설정할 수 있다는 글을 확인했다.

Change jenkins container deployment root path

Jenkins Container 생성 당시 입력했던 환경 변수를 확인해보자.

Host OS에서 아래의 명령어로 컨테이너 생성 당시 입력했던 환경변수를 확인 가능하다.

docker exec jenkins(컨테이너 이름) /usr/bin/env
PATH=/opt/java/openjdk/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=6719f87953bd
JENKINS_OPTS=--httpPort=8080
TZ=Asia/Seoul
LANG=C.UTF-8
JENKINS_HOME=/var/jenkins_home
JENKINS_SLAVE_AGENT_PORT=50000
REF=/usr/share/jenkins/ref
JENKINS_VERSION=2.447
JENKINS_UC=https://updates.jenkins.io
JENKINS_UC_EXPERIMENTAL=https://updates.jenkins.io/experimental
JENKINS_INCREMENTALS_REPO_MIRROR=https://repo.jenkins-ci.org/incrementals
COPY_REFERENCE_FILE_LOG=/var/jenkins_home/copy_reference_file.log
JAVA_HOME=/opt/java/openjdk
HOME=/root

위의 글에서 확인한 접속 prefix를 추가하기 위해서는 아래의 환경변수를 추가하면 된다.

JENKINS_OPTS="--prefix=/jenkins"

나는 아래의 문서 일부를 읽은 후 지원하지 않는 기능인 줄 알았는데, 최근 댓글을 보면 실행중인 컨테이너 위에 환경변수를 추가할 수 있는 방법이 있는 것 같다.

How to set an environment variable in a running docker container

필자의 경우 컨테이너에 환경변수를 추가하여 다시 올리는 방식으로 작업했다.

docker run -d --env JENKINS_OPTS="--httpPort=8080 --prefix=/build" --prefix=/build -v /etc/localtime:/etc/localtime:ro -e TZ=Asia/Seoul -p 8080:8080 -v /jenkins:/var/jenkins_home -v /var/run/docker.sock:/var/run/docker.sock -v /usr/local/bin/docker-compose:/usr/local/bin/docker-compose --name jenkins -u root jenkins/jenkins:jdk17

또한 path/build 경로로 들어오는 요청을 nginx에서 8080 포트로 redirect 하도록 설정해줬다.

sudo vim /etc/nginx/sites-enabmed/default

...

location /build/ {
                proxy_pass <http://localhost:8080/build/>;
        }
...

이후 nginx를 재시작 해줬다.

sudo service nginx restart

문제

시간 제한 메모리 제한 제출 정답 맞힌 사람 정답 비율

2 초 512 MB 61274 33242 22550 53.653%

문제

로봇 청소기와 방의 상태가 주어졌을 때, 청소하는 영역의 개수를 구하는 프로그램을 작성하시오.

로봇 청소기가 있는 방은 NxM 크기의 직사각형으로 나타낼 수 있으며, 1x1 크기의 정사각형 칸으로 나누어져 있다. 각각의 칸은 벽 또는 빈 칸이다. 청소기는 바라보는 방향이 있으며, 이 방향은 동, 서, 남, 북 중 하나이다. 방의 각 칸은 좌표 (r, c)로 나타낼 수 있고, 가장 북쪽 줄의 가장 서쪽 칸의 좌표가 (0, 0) 가장 남쪽 줄의 가장 동쪽 칸의 좌표가 (N-1, M-1)이다. 즉, 좌표(r, c)는 북쪽에서 (r+1) 번째에 있는 줄의 서쪽에서 (c+1) 번째 칸을 가리킨다. 처음에 빈 칸은 전부 청소되지 않은 상태이다.

로봇 청소기는 다음과 같이 작동한다.

  1. 현재 칸이 아직 청소되지 않은 경우, 현재 칸을 청소한다.
  2. 현재 칸의 주변 4칸 중 청소되지 않은 빈 칸이 없는 경우,
    1. 바라보는 방향을 유지한 채로 한 칸 후진할 수 있다면 한 칸 후진하고 1번으로 돌아간다.
    2. 바라보는 방향의 뒤쪽 칸이 벽이라 후진할 수 없다면 작동을 멈춘다.
  3. 현재 칸의 주변 4칸 중 청소되지 않은 빈 칸이 있는 경우,
    1. 반시계 방향으로 90∘ 회전한다.
    2. 바라보는 방향을 기준으로 앞쪽 칸이 청소되지 않은 빈 칸인 경우 한 칸 전진한다.
    3. 1번으로 돌아간다.

입력

첫째 줄에 방의 크기 N과 M이 입력된다. (3≤N,M≤50) 둘째 줄에 처음에 로봇 청소기가 있는 칸의 좌표 (r, c)와 처음에 로봇 청소기가 바라보는 방향 d가 입력된다. d가 0인 경우 북쪽, 1인 경우 동쪽, 2인 경우 남쪽, 3인 경우 서쪽을 바라보고 있는 것이다.

셋째 줄부터 N개의 줄에 각 장소의 상태를 나타내는 NxM개의 값이 한 줄에 M개씩 입력된다. i번째 줄의 j번째 값은 칸 (i, j)의 상태를 나타내며, 이 값이 0인 경우 (i, j)가 청소되지 않은 빈 칸이고, 1인 경우 (i, j)에 벽이 있는 것이다. 방의 가장 북쪽, 가장 남쪽, 가장 서쪽, 가장 동쪽 줄 중 하나 이상에 위치한 모든 칸에는 벽이 있다. 로봇 청소기가 있는 칸은 항상 빈 칸이다.

출력

로봇 청소기가 작동을 시작한 후 작동을 멈출 때까지 청소하는 칸의 개수를 출력한다.

풀이

구현 문제이며, 문제만 잘 읽는다면 어려움 없이 풀이할 수 있다.

코드

package com.company.baekjoon;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class B14503 {

    static int[] dx = new int[] {-1,1,0,0};
    static int[] dy = new int[] {0,0,1,-1};

    static int NOT_CLEANED = 0;
    static int WALL = 1;
    static int CLEANED = 2;
    static int n;
    static int m;
    static int[][] arr;

    static int r;
    static int c;
    static int d;
    static int count;
    static boolean state = true;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        n = Integer.parseInt(st.nextToken());
        m = Integer.parseInt(st.nextToken());
        arr = new int[n][m];

        st = new StringTokenizer(br.readLine());

        r = Integer.parseInt(st.nextToken());
        c = Integer.parseInt(st.nextToken());
        d = Integer.parseInt(st.nextToken());

        for (int i = 0; i < n; i++) {
            st = new StringTokenizer(br.readLine());
            for (int j = 0; j < m; j++) {
                int val = Integer.parseInt(st.nextToken());
                arr[i][j] = val;
            }
        }
        while (state) {
            // 1. 현재 칸이 아직 청소되지 않은 경우, 현재 칸을 청소한다.
            cleanCurrent();

            if (checkCleaned() == 4) { // 2.현재 칸의 주변 4칸 중 청소되지 않은 빈 칸이 없는 경우
                checkBack();
            }
            else if (checkNotCleaned() > 0) { // 3. 현재 칸의 주변 4칸 중 청소되지 않은 빈 칸이 있는 경우
                rotate();
                goToForward();
            }
        }
        System.out.println(count);

    }
    public static void cleanCurrent() {
        if (arr[r][c] == NOT_CLEANED) {
            arr[r][c] = CLEANED;
            count++;
        }
    }
    public static void rotate() {
        if (d == 0) {
            d = 3;
        } else if (d == 1) {
            d = 0;
        } else if (d == 2) {
            d = 1;
        } else if (d == 3) {
            d = 2;
        }
    }
    public static void checkBack() {
        if (d == 0) { // 북
            if (arr[r+1][c] == WALL) {
                state = false;
            } else {
                r = r + 1;
            }
        } else if (d == 1) { // 동
            if (arr[r][c-1] == WALL) {
                state = false;
            } else {
                c = c - 1;
            }
        } else if (d == 2) { // 남
            if (arr[r-1][c] == WALL) {
                state = false;
            } else {
                r = r - 1;
            }
        } else if (d == 3) { // 서
            if (arr[r][c+1] == WALL) {
                state = false;
            } else {
                c = c + 1;
            }
        }
    }
    public static int checkNotCleaned() {
        int notCleanedCount = 0;
        if (arr[r+1][c] != CLEANED) {
            notCleanedCount++;
        }
        if (arr[r-1][c] != CLEANED) {
            notCleanedCount++;
        }
        if (arr[r][c+1] != CLEANED) {
            notCleanedCount++;
        }
        if (arr[r][c-1] != CLEANED) {
            notCleanedCount++;
        }
        return notCleanedCount;
    }
    public static void goToForward() {
        if (d == 0) { // 북
            if (arr[r-1][c] == NOT_CLEANED) {
                r = r - 1;
            }
        } else if (d == 1) { // 동
            if (arr[r][c+1] == NOT_CLEANED) {
                c = c + 1;
            }
        } else if (d == 2) { // 남
            if (arr[r+1][c] == NOT_CLEANED) {
                r = r + 1;
            }
        } else if (d == 3) { // 서
            if (arr[r][c-1] == NOT_CLEANED) {
                c = c - 1;
            }
        }
    }
    public static int checkCleaned() {
        int cleanedCount = 0;
        if (arr[r+1][c] != NOT_CLEANED) {
            cleanedCount++;
        }
        if (arr[r-1][c] != NOT_CLEANED) {
            cleanedCount++;
        }
        if (arr[r][c-1] != NOT_CLEANED) {
            cleanedCount++;
        }
        if (arr[r][c+1] != NOT_CLEANED) {
            cleanedCount++;
        }
        return cleanedCount;
    }
}

문제

시간 제한 메모리 제한 제출 정답 맞힌 사람 정답 비율

1 초 256 MB 166553 64421 47778 36.624%

문제

2×n 크기의 직사각형을 1×2, 2×1 타일로 채우는 방법의 수를 구하는 프로그램을 작성하시오.

아래 그림은 2×5 크기의 직사각형을 채운 한 가지 방법의 예이다.

입력

첫째 줄에 n이 주어진다. (1 ≤ n ≤ 1,000)

출력

첫째 줄에 2×n 크기의 직사각형을 채우는 방법의 수를 10,007로 나눈 나머지를 출력한다.

풀이

아래처럼 실제로 직사각형을 채우는 방법을 나열 해본 후 n번째 직사각형을 채우는 방법은 n-1번째와 n-2번째의 합이라는 것을 알 수 있었다.

이 후 방법의 수를 10,007로 나누어 출력해줬다.

1 - 1
1

2 - 2
1 1
2 2

3 - 3
1 1 1
1 2 2
2 2 1

4 - 5
1 1 1 1
1 2 2 1
2 2 1 1
1 1 2 2
2 2 2 2

5 - 8  
1 1 1 1 1
1 2 2 1 1
2 2 1 1 1
1 1 2 2 1
2 2 2 2 1
1 1 1 2 2
1 2 2 2 2
2 2 1 2 2

 

코드

package com.company.baekjoon;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class B11726 {
	static long[] dp = new long[1001];
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());

		int n = Integer.parseInt(st.nextToken());

		dp[1] = 1;
		dp[2] = 2;
		dp[3] = 3;
		for (int i = 4; i <= n; i++) {
			dp[i] = (dp[i-1] % 10007) + (dp[i-2] % 10007);
		}

		System.out.println(dp[n] % 10007);
	}
}

문제

시간 제한 메모리 제한 제출 정답 맞힌 사람 정답 비율

2 초 128 MB 42619 16415 12036 37.043%

문제

동혁이는 친구들과 함께 여행을 가려고 한다. 한국에는 도시가 N개 있고 임의의 두 도시 사이에 길이 있을 수도, 없을 수도 있다. 동혁이의 여행 일정이 주어졌을 때, 이 여행 경로가 가능한 것인지 알아보자. 물론 중간에 다른 도시를 경유해서 여행을 할 수도 있다. 예를 들어 도시가 5개 있고, A-B, B-C, A-D, B-D, E-A의 길이 있고, 동혁이의 여행 계획이 E C B C D 라면 E-A-B-C-B-C-B-D라는 여행경로를 통해 목적을 달성할 수 있다.

도시들의 개수와 도시들 간의 연결 여부가 주어져 있고, 동혁이의 여행 계획에 속한 도시들이 순서대로 주어졌을 때 가능한지 여부를 판별하는 프로그램을 작성하시오. 같은 도시를 여러 번 방문하는 것도 가능하다.

입력

첫 줄에 도시의 수 N이 주어진다. N은 200이하이다. 둘째 줄에 여행 계획에 속한 도시들의 수 M이 주어진다. M은 1000이하이다. 다음 N개의 줄에는 N개의 정수가 주어진다. i번째 줄의 j번째 수는 i번 도시와 j번 도시의 연결 정보를 의미한다. 1이면 연결된 것이고 0이면 연결이 되지 않은 것이다. A와 B가 연결되었으면 B와 A도 연결되어 있다. 마지막 줄에는 여행 계획이 주어진다. 도시의 번호는 1부터 N까지 차례대로 매겨져 있다.

출력

첫 줄에 가능하면 YES 불가능하면 NO를 출력한다.

풀이

예제의 여행 계획을 보면 각 노드들이 모두 연결되어 있기만 하면 어떻게든 경유하여 목적을 달성할 수 있다. 따라서, dfs 또는 bfs를 이용하여 연결된 노드를 모두 탐색한다. 나의 경우 dfs를 통해 풀었고, 검색 기준은 여행계획의 첫번째 도시로 설정했다.

코드

package com.company.baekjoon;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class B1976 {
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());

		int N = Integer.parseInt(st.nextToken());
		st = new StringTokenizer(br.readLine());

		int [][] graph = new int[N][N];
		boolean[] visited = new boolean[N];
		boolean[] target = new boolean[N];
		int M = Integer.parseInt(st.nextToken());

		for (int i = 0; i < N; i++) {
			st = new StringTokenizer(br.readLine());

			for (int j = 0; j < N; j++) {
				graph[i][j] = Integer.parseInt(st.nextToken());
			}
		}

		st = new StringTokenizer(br.readLine());

		int start = 0;
		boolean checkStart = false;
		while (st.hasMoreTokens()) {
			int val = Integer.parseInt(st.nextToken()) - 1;
			if (!checkStart) {
				start = val;
				checkStart = true;
			}
			target[val] = true;
		}

		dfs(graph,visited, start);
		boolean isTravel = checkCity(visited, target);

		if (isTravel) {
			System.out.println("YES");
		} else {
			System.out.println("NO");
		}
	}
	public static boolean checkCity(boolean[] visited, boolean[] target) {
		boolean isTravel = false;
		for (int i = 0; i <visited.length; i++) {
			if (target[i]) { // 꼭 들러야 하는곳이라면
				if (visited[i]) {
					isTravel = true;
				} else {
					isTravel = false;
					break;
				}
			}
		}
		return isTravel;
	}
	public static void dfs(int[][] graph, boolean[] visited, int start) {
		visited[start] = true;
		for (int i = 0; i < graph.length; i++) {
			if (!visited[i] && graph[start][i] == 1) {
				dfs(graph, visited, i);
			}
		}
	}
}

문제

시간 제한 메모리 제한 제출 정답 맞힌 사람 정답 비율

2 초 128 MB 39301 13232 10581 32.815%

문제

길이가 N인 수열이 주어졌을 때, 그 수열의 합을 구하려고 한다. 하지만, 그냥 그 수열의 합을 모두 더해서 구하는 것이 아니라, 수열의 두 수를 묶으려고 한다. 어떤 수를 묶으려고 할 때, 위치에 상관없이 묶을 수 있다. 하지만, 같은 위치에 있는 수(자기 자신)를 묶는 것은 불가능하다. 그리고 어떤 수를 묶게 되면, 수열의 합을 구할 때 묶은 수는 서로 곱한 후에 더한다.

예를 들면, 어떤 수열이 {0, 1, 2, 4, 3, 5}일 때, 그냥 이 수열의 합을 구하면 0+1+2+4+3+5 = 15이다. 하지만, 2와 3을 묶고, 4와 5를 묶게 되면, 0+1+(23)+(45) = 27이 되어 최대가 된다.

수열의 모든 수는 단 한번만 묶거나, 아니면 묶지 않아야한다.

수열이 주어졌을 때, 수열의 각 수를 적절히 묶었을 때, 그 합이 최대가 되게 하는 프로그램을 작성하시오.

입력

첫째 줄에 수열의 크기 N이 주어진다. N은 50보다 작은 자연수이다. 둘째 줄부터 N개의 줄에 수열의 각 수가 주어진다. 수열의 수는 -1,000보다 크거나 같고, 1,000보다 작거나 같은 정수이다.

출력

수를 합이 최대가 나오게 묶었을 때 합을 출력한다. 정답은 항상 2(31)보다 작다.

풀이

먼저 정답은 항상 2의 31제곱보다 작으므로 int형으로 저장한다.

또한 문제를 세가지 경우로 나눠서 큐에 담았고 각각 처리를 해줬다.

  1. 양수인 경우
    1. 1개만 꺼낼 수 있는 경우 덧셈 해준다.
    2. 2개 꺼낼 수 있고, 꺼낸 수가 2개 다 1이 아닌 양수인 경우 무조건 곱셈을 하는것이 높은 수를 얻을 수 있다.
    3. 2개 꺼낼 수 있고, 꺼낸 수에 1개 이상 1이 있는경우 덧셈을 하는것이 좋다.
  2. 음수인 경우
    1. 2개 꺼낼 수 있고, 꺼낸 수가 2개 다 음수인 경우 무조건 곱셈을 하는것이 높은 수를 얻을 수 있다.
    2. 1개 꺼낼 수 있고, 0이 든 큐에 데이터가 있을 경우 곱셈하여 음수를 없애준다.
    3. 1개 꺼낼 수있고, 0이 든 큐에 데이터가 없을 경우 음수를 더해준다.

코드

package com.company.baekjoon;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.StringTokenizer;

public class B1744 {
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());

		int N = Integer.parseInt(st.nextToken());

		PriorityQueue<Integer> q = new PriorityQueue<>(Comparator.reverseOrder());
		PriorityQueue<Integer> rq = new PriorityQueue<>();
		Queue<Integer> zeroq = new LinkedList<>();
		for (int i = 0; i < N; i++) {
			st = new StringTokenizer(br.readLine());

			int value = Integer.parseInt(st.nextToken());
			if (value > 0) {
				q.add(value);
			} else if (value == 0) {
				zeroq.add(value);
			} else {
				rq.add(value);
			}

		}
		int answer = 0;
		while (!q.isEmpty()) {
			int first = q.poll();
			if (q.isEmpty()) {
				answer += first;
				break;
			}
			int second = q.poll();
			if (first == 1 || second == 1)
				answer += first + second;
			else
				answer += first * second;
		}

		while (!rq.isEmpty()) {
			int first = rq.poll();
			if (rq.isEmpty()) {
				if (!zeroq.isEmpty()) {
					zeroq.poll();
				} else {
					answer += first;
				}
				break;
			}
			int second = rq.poll();

			answer += first * second;
		}
		System.out.println(answer);
	}
}

Docker 란?

Docker는 애플리케이션을 신속하게 구축, 테스트 및 배포할 수 있는 소프트웨어 플랫폼

  • Docker는 소프트웨어를 컨테이너라는 표준화된 유닛으로 패키징하며, 이 컨테이너에는 라이브러리, 시스템 도구, 코드, 런타임 등 소프트웨어를 실행하는 데 필요한 모든 것이 포함
  • Docker를 사용하면 환경에 구애받지 않고 애플리케이션을 신속하게 배포 및 확장

Docker 작동 방식

Docker는 컨테이너를 위한 운영 체제

가상 머신이 서버 하드웨어를 가상화하는 방식과 비슷하게(직접 관리해야 하는 필요성 제거) 컨테이너는 서버 운영 체제를 가상화

Untitled

Container VS VM ?

Container의 장점

  • VM 보다 볼륨이 작다.
    • 위의 그림에서 보이듯이, Container는 Host의 커널을 공유한다. 따라서, 가상화된 하드웨어위에 OS가 올라가는 VM보다 볼륨이 작다.
  • 빠르다.
    • I/O가 발생하는 통로가 VM이 더 많을 수 밖에 없다. VM이 처리한 I/O를 Host OS의 커널에서 다시 받아 자신의 드라이버에 맞게 처리해줘야 한다. Container의 경우 커널을 공유하므로 들어온 I/O가 보다 쉽게 처리된다.
  • 라이프사이클
    • 개발 환경 내에서 image 만들어 배포하고 pull 받아서 다시 올리면 끝. 매우 편리하게 서비스를 관리할 수 있다.
    • 여러대의 host에서 실행중인 container를 관리할 수 있는 오케스트레이션 제공

→ MicroService로 발전할 수 있는 토대를 마련하고 적용해 나갈 수 있다.

VM의 장점

  • 보안
    • Container가 host 커널을 공유한다는 것은 container가 뚫리면 바로 host OS의 커널이 위험해질 수 있다. 동시에 호스트 커널까지 오지 않더라도 커널이 공격당하면, 커널을 공유한다는 개념 때문에 모든 Container가 위험해질 수 있다. 반면, VM의 경우 완벽하게 다른 VM이나 host가 보호된다.
  • 멀티 OS
    • 커널을 공유한다는 이유로 Container에서 할 수 없는 일이 바로 Host OS와 다른 OS를 올릴 수 없다는 점이있다.

Reference

Docker란 무엇입니까? | AWS

Docker와 VM

'Computer Science' 카테고리의 다른 글

SSL이란?  (0) 2024.02.19
HLS(HTTP 라이브 스트리밍)란?  (0) 2023.12.28
서버 이중화란? (HA, 클러스터링, Fail-Over)  (0) 2023.10.08

개요

SSL(Secure Sockets Layer)은 암호화 기반 인터넷 보안 프로토콜입니다. 인터넷 통신의 개인정보 보호, 인증, 데이터 무결성을 보장하기 위해 Netscape가 1995년 처음으로 개발했습니다. SSL은 현재 사용 중인 TLS 암호화의 전신입니다.

SSL/TLS를 사용하는 웹사이트의 URL에는 "HTTP" 대신 "HTTPS"가 있습니다.

SSL/TLS는 어떻게 작동합니까?

  • SSL은 높은 수준의 개인정보 보호를 제공하기 위해, 웹에서 전송되는 데이터를 암호화합니다. 따라서, 데이터를 가로채려는 자는 거의 해독할 수 없는 복잡한 문자만 보게 됩니다.
  • SSL은 두 통신 장치 사이에 핸드셰이크라는 인증 프로세스를 시작하여 두 장치의 ID를 확인합니다.
  • SSL은 또한 데이터 무결성을 제공하기 위해 데이터에 디지털 서명하여 데이터가 의도된 수신자에 도착하기 전에 조작되지 않았다는 것을 확인합니다.

SSL은 여러 번 개선되어 매번 성능이 개선됐습니다. 1999년에 SSL은 TLS로 업데이트됐습니다.

SSL/TLS는 왜 중요합니까?

원래 웹 상의 데이터는 메시지를 가로채면 누구나 읽을 수 있는 일반 텍스트 형태로 전송됐습니다. 가령 고객이 쇼핑 웹사이트를 방문하여 주문하고, 신용 카드 번호를 입력했다고 하면, 해당 신용 카드 번호가 숨겨지지 않은 채 인터넷을 이동하게 됩니다.

SSL은 이 문제를 바로잡고, 사용자 개인 정보를 보호하기 위해 제작됐습니다. SSL은 사용자와 웹 서버 사이를 이동하는 모든 데이터를 암호화하여, 누군가 데이터를 가로채더라도 무작위 문자만 볼 수 있게 합니다. 이제 고객의 신용 카드 번호는 안전해졌으며, 고객이 번호를 입력한 쇼핑 웹사이트만 이를 볼 수 있습니다.

SSL은 특정한 유형의 사이버 공격도 차단합니다. SSL은 웹 서버를 인증하는데, 공격자들이 사용자를 속여 데이터를 훔치기 위한 가짜 웹사이트를 만드는 일이 있기 때문에, 이러한 인증이 중요합니다. 또한 약병의 조작 방지 봉인처럼, 공격자가 전송 중인 데이터를 조작하지 못하게 막기도 합니다.

SSL 인증이란 무엇입니까?

SSL은 SSL 인증서(공식적으로 "TLS 인증서")가 있는 웹사이트만 실행할 수 있습니다. SSL 인증서는 사람의 신원을 확인하는 신분증이나 배지와 같습니다. SSL 인증서는 웹사이트나 애플리케이션 서버가 웹에 저장하고 표시합니다.

SSL 인증서에 포함된 가장 중요한 정보 중 하나가 웹 사이트의 공개 입니다. 이 공개 키 덕분에 암호화와 인증이 가능합니다. 사용자의 장치는 공개 키를 보고 이를 이용하여 웹 서버와 안전한 암호화 키를 수립합니다. 한편, 웹 서버에도 기밀로 유지하는 개인 키가 있습니다. 개인 키는 공개 키로 암호화된 데이터를 해독합니다.

CA(인증 기관)는 SSL 인증서 발행을 담당합니다.

SSL의 동작 원리

Untitled

  1. 핸드셰이크 - 데이터를 주고받기 위해서 어떤 방법을 사용해야 하는지 서로 파악한다.
    1. SSL은 80 포트를 사용하는 http와 달리 443 포트를 기본으로 사용하는 TCP 기반의 프로토콜이다. 따라서, SSL 핸드셰이크 전에 TCP 3-way 핸드셰이크 또한 수행한다.
  2. 전송 - SSL 세션이 생성되고 클라이언트와 서버가 원하는 데이터를 주고받는다.
  3. 종료 - 데이터 전송의 끝을 서로 알리며 세션을 종료한다.

핸드셰이크 단계

Untitled

  1. Client Hello - 클라이언트가 서버에게 연락한다.
    1. 클라이언트는 자신의 브라우저가 지원할 수 있는 암호화 방식을 먼저 제시한다.
    2. 랜덤 데이터를 생성하여 추가로 전송한다.
  2. Server Hello - 서버가 클라이언트에게 연락한다.
    1. 클라이언트가 제시한 암호화 방식 중 하나를 선정하여 알려준다.
    2. 서버 자신의 인증서를 전달한다. (이 인증서에는 서버의 공개키가 포함되어 있다.)
    3. 클라이언트와 마찬가지로 서버에서 생성한 랜덤 데이터를 전송한다.
  3. Client Key Exchange - 클라이언트는 미리 주고받은 자신과 서버의 랜덤 데이터를 참고하여 서버와 암호화 통신 할 때 사용할 키를 생성하여 서버에 전달한다.
    1. 키는 서버로부터 받은 공개키로 암호화되어 보내진다.
  4. Finished - 핸드셰이크 과정이 정상적으로 마무리되면, 클라이언트와 서버 모두 “finished” 메시지를 보낸다.

SSL 적용하기

사전 준비

  1. NginX 버전
  2. 서버(EC2, LightSail) 준비
  3. 실제 존재하는 도메인
    1. 우리 서비스의 경우 싸피에서 발급받은 주소 사용

실습코치님 추천 인증기관

  • zeroSSL (90일 무료)
  • certBot (무료)
  • letsEncrypt (무료)

인증서 발급 방법

조은비 코치님의 경우 아래의 링크를 따라서 발급 받았다고 함.

[Let's encrypt] 인증서 발급 및 갱신법

필자 개인적인 궁금증에 대한 정리

  • OpenSSL과 CA의 관계는?

    OpenSSL vs CA

    1. 인증 기관 대 라이브러리 : Let's Encrypt는 주로 무료 SSL/TLS 인증서를 제공하는 인증 기관인 반면, OpenSSL은 암호화 기능과 프로토콜을 제공하는 소프트웨어 라이브러리입니다.

    lets encrypt = 비행기

    openssl = 비행기 엔진

  • SSL은 HTTPS를 위해서만 사용하는가?

    Untitled

    SSL과 TLS는 ‘보안 계층’ 이라는 독립적인 프로토콜 계층을 만들어, 위 그림과 같이 응용 계층과 전송 계층 사이에 속하게 된다.

  • TCP/UDP와 같은 소켓 통신에도 SSL을 사용할 수 있는가?

    → 아래의 링크에 따르면 가능한것으로 보임.

    Adding SSL support to existing TCP & UDP code?

    [JAVA] 자바 SSL소켓통신 예제 (Java SSLServerSocket Example)

  • Self Signed Certificate?

    인증서(digital certificate)는 개인키 소유자의 공개키(public key)에 인증기관의 개인키로 전자서명한 데이타다.

    모든 인증서는 발급기관(CA) 이 있어야 하나 최상위에 있는 인증기관(root ca)은 서명해줄 상위 인증기관이 없으므로 root ca의 개인키로 스스로의 인증서에 서명하여 최상위 인증기관 인증서를 만든다.

    이렇게 스스로 서명한 ROOT CA 인증서를 Self Signed Certificate(SSC) 라고 부른다.

    IE, FireFox, Chrome 등의 Web Browser 제작사는 VeriSign 이나 comodo 같은 유명 ROOT CA 들의 인증서를 신뢰하는 CA로 브라우저에 미리 탑재해 놓는다.

    저런 기관에서 발급된 SSL 인증서를 사용해야 browser 에서는 해당 SSL 인증서를 신뢰할수 있는데 OpenSSL 로 만든 ROOT CA와 SSL 인증서는 Browser가 모르는 기관이 발급한 인증서이므로 보안 경고를 발생시킬 것이나 테스트 사용에는 지장이 없다.

    ROOT CA 인증서를 Browser에 추가하여 보안 경고를 발생시키지 않으려면 Browser 에 SSL 인증서 발급기관 추가하기 를 참고하자.

Reference

SSL 보안을 위한 SSL 인증서 | Cloudflare

HTTPS와 SSL 인증서 - 생활코딩

웹사이트 보안을 위한 방법, SSL이란? (feat. SSL과 HTTPS의 차이)

'Computer Science' 카테고리의 다른 글

Docker란?  (1) 2024.02.19
HLS(HTTP 라이브 스트리밍)란?  (0) 2023.12.28
서버 이중화란? (HA, 클러스터링, Fail-Over)  (0) 2023.10.08

HLS란?

HLS(HTTP 라이브 스트리밍)은 가장 널리 사용되는 비디오 스트리밍 프로토콜로써, 비디오 파일을 다운로드할 수 있는 HTTP 파일 조각으로 나누고 HTTP 프로토콜을 이용하여 전송한다.

HLS의 장점

  • 모든 인터넷 연결 장치가 HTTP를 지원하기 때문에 전용 서버가 필요한 스트리밍 프로토콜보다 간단하게 실행할 수 있다.
  • 재생에 지장을 주지 않고 네트워크 상태에 따라 비디오 품질을 높이거나 낮출 수 있다. (적응형 비트 전송률 비디오 전송) 이 기능이 없으면 네트워크가 느려진 경우 비디오 재생이 완전히 멈출 수 있다.

HLS는 어떻게 작동하는가

서버 : HLS 스트리밍은 미디어 파일이 저장된 서버스트리밍이 제작된 서버에서 시작된다.

서버에서 두 가지 주요 프로세스가 진행된다.

  1. 인코딩 : 비디오 데이터의 포맷을 다시 설정하여 모든 장치가 데이터를 인식하고 해석할 수 있게 한다. HLS는 H.264나 H.265 인코딩을 사용해야 한다.
  2. 조각화 : 비디오는 몇 초 길이의 세그먼트로 나뉜다.
  3. 비디오를 세그먼트로 나누는 것과 더불어 HLS는 비디오 세그먼트의 인덱스 파일을 만들어 세그먼트의 순서를 기록한다.
  4. HLS는 또한 480p, 720p, 1080p 등의 다양한 품질로 여러 세트의 세그먼트를 복제한다.

배포 : 인코딩된 비디오 세그먼트는 클라이언트 장치가 스트리밍을 요청하면 인터넷을 통해 클라이언트 장치로 전송된다.

클라이언트 장치 : 클라이언트 장치는 인덱스 파일을 참조하여 비디오를 순서대로 조합하고 필요에 따라 품질을 높이거나 낮춘다.

HLS로 영상을 인코딩 할 경우 m3u8 확장자와 ts 확장자를 가진 파일이 생성된다.

  • m3u8 파일은 영상 재생을 위한 메타 정보들이 담겨 있다.
  • ts 파일은 실제 스트리밍 영상 데이터이며, 시간 단위로 작게 쪼개져 있다.

m3u8 파일 예시

아래는 내가 개발중인 서비스의 영상 중 master m3u8 파일을 가져와 봤다.

3가지의 품질로 영상이 준비되어 있고, bandwidth에 따라서 품질을 선택할 수 있도록 작성되어 있다.

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=5640800,RESOLUTION=1080x1920,CODECS="avc1.640028,mp4a.40.2"
1080playlist.m3u8

#EXT-X-STREAM-INF:BANDWIDTH=2855600,RESOLUTION=720x1280,CODECS="avc1.64001f,mp4a.40.2"
720playlist.m3u8

#EXT-X-STREAM-INF:BANDWIDTH=1170400,RESOLUTION=480x854,CODECS="avc1.64001f,mp4a.40.2"
480playlist.m3u8

마무리

여러 영상 스트리밍 서비스를 둘러봤을 때 가장 범용적으로 사용하는 HLS 방식의 영상 스트리밍에 대해 간단히 알아봤다. 많은 기업이 HLS 방식을 이용한 스트리밍을 활용하고 있는 만큼 장점이 확실한 것 같다. (어댑티브 스트리밍, 여러 장치 지원)

Reference

동영상 플랫폼 이해하기 (1) - HLS

HLS (HTTP 라이브 스트리밍)이란 | Cloudflare

'Computer Science' 카테고리의 다른 글

Docker란?  (1) 2024.02.19
SSL이란?  (0) 2024.02.19
서버 이중화란? (HA, 클러스터링, Fail-Over)  (0) 2023.10.08

문제

문제 설명

계속되는 폭우로 일부 지역이 물에 잠겼습니다. 물에 잠기지 않은 지역을 통해 학교를 가려고 합니다. 집에서 학교까지 가는 길은 m x n 크기의 격자모양으로 나타낼 수 있습니다.

아래 그림은 m = 4, n = 3 인 경우입니다.

!https://grepp-programmers.s3.amazonaws.com/files/ybm/056f54e618/f167a3bc-e140-4fa8-a8f8-326a99e0f567.png

가장 왼쪽 위, 즉 집이 있는 곳의 좌표는 (1, 1)로 나타내고 가장 오른쪽 아래, 즉 학교가 있는 곳의 좌표는 (m, n)으로 나타냅니다.

격자의 크기 m, n과 물이 잠긴 지역의 좌표를 담은 2차원 배열 puddles이 매개변수로 주어집니다. 오른쪽과 아래쪽으로만 움직여 집에서 학교까지 갈 수 있는 최단경로의 개수를 1,000,000,007로 나눈 나머지를 return 하도록 solution 함수를 작성해주세요.

제한사항

  • 격자의 크기 m, n은 1 이상 100 이하인 자연수입니다.
    • m과 n이 모두 1인 경우는 입력으로 주어지지 않습니다.
  • 물에 잠긴 지역은 0개 이상 10개 이하입니다.
  • 집과 학교가 물에 잠긴 경우는 입력으로 주어지지 않습니다.

입출력 예

m n puddles return

4 3 [[2, 2]] 4

 

풀이

DP 문제에 자신이 없어서 도전했는데, 어떻게 접근해야 할지 몰라서 풀이를 찾아 봤다.

먼저 오른쪽과 아래쪽으로만 움직여 집에서 학교까지 갈 수 있는 최단 경로를 구하는 것 이므로, 왼쪽 또는 위로 이동을 고려하지 않아야 한다.

위의 사진을 보면, 오른쪽과 아래쪽으로 이동하는 경로가 있고, 화살표를 잘 보면 1,3 지점에서 아래쪽으로 두가지의 화살표, 2,2 지점에서 오른쪽으로 두가지의 화살표가 있는것을 볼 수 있다.

이를보면 i,j 위치의 경로는 [i-1,j] [i,j-1] 경로의 합 이라는 것을 알 수 있다.

이를통해 점화식을 세우고, 만약 현재 위치가 물에 잠긴 지역일경우 이동할 수 없으므로 스킵하도록 했다.

DP문제를 더 많이 접할 필요를 느꼈다…

코드

class Solution {
    public int solution(int m, int n, int[][] puddles) {
        int mod = 1000000007;
        int[][] arr = new int[n+1][m+1];

        for (int i = 0; i < puddles.length; i++) {
            int[] puddle = puddles[i];
            arr[puddle[1]][puddle[0]] = -1;
        }
        arr[1][1] = 1;
        for (int i = 1; i <= n; i++) {
            for(int j = 1; j <= m; j++) {
                if(arr[i][j] == -1) continue;
                if(arr[i - 1][j] > 0) arr[i][j] += arr[i - 1][j] % mod;
                if(arr[i][j - 1] > 0) arr[i][j] += arr[i][j - 1] % mod;
            }
        }
        return arr[n][m] % mod;
    }
}

문제

문제 설명

두 개의 단어 begin, target과 단어의 집합 words가 있습니다. 아래와 같은 규칙을 이용하여 begin에서 target으로 변환하는 가장 짧은 변환 과정을 찾으려고 합니다.

1. 한 번에 한 개의 알파벳만 바꿀 수 있습니다. 2. words에 있는 단어로만 변환할 수 있습니다.

예를 들어 begin이 "hit", target가 "cog", words가 ["hot","dot","dog","lot","log","cog"]라면 "hit" -> "hot" -> "dot" -> "dog" -> "cog"와 같이 4단계를 거쳐 변환할 수 있습니다.

두 개의 단어 begin, target과 단어의 집합 words가 매개변수로 주어질 때, 최소 몇 단계의 과정을 거쳐 begin을 target으로 변환할 수 있는지 return 하도록 solution 함수를 작성해주세요.

제한사항

  • 각 단어는 알파벳 소문자로만 이루어져 있습니다.
  • 각 단어의 길이는 3 이상 10 이하이며 모든 단어의 길이는 같습니다.
  • words에는 3개 이상 50개 이하의 단어가 있으며 중복되는 단어는 없습니다.
  • begin과 target은 같지 않습니다.
  • 변환할 수 없는 경우에는 0를 return 합니다.

입출력 예

begin target words return

"hit" "cog" ["hot", "dot", "dog", "lot", "log", "cog"] 4
"hit" "cog" ["hot", "dot", "dog", "lot", "log"] 0

입출력 예 설명

예제 #1

문제에 나온 예와 같습니다.

예제 #2

target인 "cog"는 words 안에 없기 때문에 변환할 수 없습니다.

풀이

BFS로 풀었다. 변환 횟수와 현재 문자열로 Pair를 만든 후에 큐에 추가한다, 해당 문자열을 변환 가능할 경우 변환 횟수를 1회 늘리고 변환 가능한 문자를 다시 큐에 추가한다. 이를 반복하여 큐에서 꺼낸 값이 target과 동일할 경우 리턴하도록 작성했다.

코드

import javafx.util.Pair;
import java.util.Comparator;
import java.util.PriorityQueue;

class Solution {
    public int solution(String begin, String target, String[] words) {
        int answer = 0;
        PriorityQueue<Pair<String,Integer>> queue = new PriorityQueue<>(new Comparator<Pair<String, Integer>>() {
            @Override
            public int compare(Pair<String, Integer> o1, Pair<String, Integer> o2) {
                return o1.getValue() - o2.getValue();
            }
        });
        boolean isContain = false;
        for (String word:
             words) {
            if (word.equals(target)) {
                isContain = true;
            }
        }
        if (!isContain)
            return 0;
        queue.add(new Pair<>(begin, 0));
        while (true) {
            Pair<String, Integer> p = queue.poll();
            if (p.getKey().equals(target)) {
                answer = p.getValue();
                break;
            }
            for (int i = 0; i < words.length; i++) {
                int diff = 0;
                for (int j = 0; j < p.getKey().length(); j++) {
                    if (words[i].charAt(j) != p.getKey().charAt(j)) {
                        diff++;
                    }
                }
                if (diff == 1) {
                    queue.add(new Pair<>(words[i], p.getValue()+1));
                }
            }
        }
        return answer;
    }
}

+ Recent posts