📌 공부 계기

추가적인 도커 공부를 위해 유튜브 따배도 도커 시리즈를 보면서 정리해 봅니다. 

목차

 



📍8-1. Docker Container Storage : 이론

1️⃣ 컨테이너 볼륨이 뭐에요?

 

컨테이너 이미지는 readonly이다

컨테이너 이미지를 컨테이너로 돌리게 되면 RW (ReadWrite) 레이어가 추가되고 컨테이너에 추가되는 데이터들이 이곳에 저장되게 된다. 

이처럼 기존의 readonly 레이어에 readwrite 레이어를 올려 마치 하나인 것처럼 관리하고 보여주게 되는데

이를 union file system(다른말로 overlay) 이라고 한다.

 

이렇게 하나처럼 운영되는 컨테이너는 컨테이너를 삭제하면 rw레이어 데이터까지 지워버리게 된다. 

데이터를 보존해야하는데!

그래서 컨테이너가 제공하는 기능이 바로 볼륨이다. 

컨테이너 rw 레이어의 디렉토리 경로와 호스트 저장소 디렉토리를 연결하여 rw레이어에 쌓일 데이터를 호스트 컴퓨터 저장소에 쌓이게 함으로써 컨테이너 삭제시에도 데이터를 보존할 수 있게 한다. 

이렇게 연결하는 걸 볼륨 마운트라고 한다. 명령어로는 run시 v옵션으로 가능하다.

docker run [-v 호스트디렉토리:컨테이너디렉토리] <이미지명:태그>

 


2️⃣ 데이터를 보존하고 싶어요

 

-volume 옵션 사용

-v <host path> : <container mount path>   기본 방식
-v <host path> : <container mount path> : <read write mode> ***
-v <container mount path> 호스트의 /var/lib/docker 밑에 UUID 디렉토리 아래로 저장시켜준다. 

*** 보안을 위해 컨테이너는 오직 호스트 경로의 데이터만 읽어오고 컨테이너에 쌓이는 게 호스트에 영향이 가지 않게 하는 것은 read write mode 자리에 ro를 (readonly)를 써주면 된다. 

 

 


3️⃣ 컨테이너끼리 데이터 공유 가능한가요?

 

하나의 컨테이너 디렉토리와 호스트 디렉토리를 연결하여 데이터를 쌓고,

또 다른 컨테이너 디렉토리와 그 호스트 디렉토리를 연결하여 데이터를 공유하면 

두 컨테이너가 같은 호스트 디렉토리를 바라보게 되어 컨테이너끼리 데이터 공유가 가능하다.  

 

ex)

컨테이너가 web content를 만들어 저장하면 다른 webserver 컨테이너가 그 생성한 web content 파일을 가지고 실행을 할 수가 있다. 클라이언트는 webserver 컨테이너에 접속하는 것 만으로도 web content 컨테이너가 제공하는 것과 webserver가 제공하는 것을 다 누릴 수 있게 된다. 

 

 

 


📍8-2. Docker Container Storage : 실습

1️⃣ mysql DB data 영구 보존하기

 

1) MySQL 컨테이너 생성 및 실행하기

*** MySQL DB 실행할 때는 관리자의 패스워드를 함께 지정해줘야한다. 

해당 경로가 없으면 디렉토리를 자동으로 생성함

# docker run [-d] [--name 컨테이너명] [-v 호스트경로:컨테이너경로] [-e 환경변수=값] <이미지:태그>
docker run -d --name db -v /dbdata:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=pass mysql:latest

2) 컨테이너 안의 MySQL에 접속해서 데이터베이스 생성해보기

컨테이너를 터미널로 연결해서 mysql에 접속 

그리고 데이터베이스 확인까지!

DB생성 및 확인

컨테이너 안에 ttabae라는 DB를 생성했으니 호스트 연결된 경로에도 ttabae가 연결됬는 지 확인하면 된다. 

호스트 경로에 들어가 조회하니 ttabae가 있는 것을 확인! 

 

3) 컨테이너를 삭제했을 때도 남았는지 확인

남아있다!

 

4) 호스트 경로 없이 컨테이너 경로만 설정하게 되면?

# docker run [-d] [--name 컨테이너명] [-v 컨테이너경로] [-e 환경변수=값] <이미지:태그>
docker run -d --name db -v /var/lib/mysql -e MYSQL_ROOT_PASSWORD=pass mysql:latest

inspect로 컨테이너 조회하면 마운트 정보에 호스트 소스는 /var/lib/docker/volumes/uuid/_data에 저장된걸 확인할 수 있다.

 

이 역시도 컨테이너를 삭제한 후에 데이터가 남는 걸 확인 할 수 있다.

/var/ 폴더는 root 계정으로 접근 가능!

 

5) docker volume 관리 명령어

 

  • 볼륨 조회
docker volume ls

 

  • 볼륨 제거
docker volume rm 볼륨UUID

해당 디렉토리가 사라져있는 걸 확인할 수 있다

 


2️⃣ 웹데이터 readonly 서비스로 지원하기

웹 컨텐츠를 생성하고 그걸 nginx로 운영하는 webserver로 서비스 할 수 있게 하는 실습

 

📢 여기서부터는 버츄얼박스와의 ip연결 문제로 이슈가 생겨서 wsl로 진행

 

1) 웹 컨텐츠 생성하기

폴더 하나 만들고 그 안에 단순 태그를 출력해 index.html을 하나 생성한다

 

2) 웹서버 서비스할 index.html을 호스트 파일로 교체

# docker run [-d] [--name 컨테이너명] [-v 호스트경로:컨테이너경로:읽기쓰기모드] /
# [-p 호스트포트:컨테이터포트] <이미지명:태그>
docker run -d --name web -v /webdata:/usr/share/nginx/html:ro -p 80:80 nginx:1.14

 


3️⃣ 컨테이너간 데이터 공유하기

 

디스크 사용량의 결과를 주기적으로 만들어내는 컨테이너를 만들어

 

* 디스크 사용량 모니터링

디스트 모니터링 할 때 사용하는 df 명령어 (disk free) 에 h옵션 (human/사람이 보기좋은 크기단위표시) 로 디스크 사용량을 모니터링 

df -h /

 

🚩 일단 이걸 실습하기 위한 파일 생성

① mkdir로 lab8이라는 폴더 생성

② lab8로 이동

③ vi로 비쥬얼에디터 열어 df.sh 파일 생성

그 안에 쉘스크립트 내용을 작성

스크립트 설명
#! /bin/bash #!은 스크립트를 실행할 쉘을 지정하는 선언문
이 스크립트는 /bin/bash라는 bash쉘을 실행한다고 하는 것
mkdir -p /webdata mkdir(make directory)로 디렉토리 생성
p옵션 (parents)은  기존 디렉토리가 존재해도 에러가 발생하지 않고 필요경우 부모 디렉토리를 생성한다. 
while true 반복문으로 조건을 true로 했기에 무한 반복
do 반복될 부분의 시작 표시
df -h / > /webdata/index.html df 명령어로 (disk free) 디스크 사용량을 보며 
h옵션(human)으로 사람이 보기 좋은 크기단위로 표시한다
> (리다이렉션)은 보통 command > filename과 같은 형태로 사용하며, 표준 입력을 전달 또는 표준 출력을 파일로 저장
따라서 디스크 사용량을 저 경로의 index.html파일로 저장한다는 뜻
sleep 10  10초 일시정지
done 반복문 중 하나로 do와 짝을 이루어 반복될 부분 닫는 역할

④ cat으로 df.sh 내용 다시 확인

⑤ dockerfile도 vi로 생성하고 cat으로 확인

스크립트 설명
FROM ubuntu:20.04 베이스이미지를 ubuntu:20.04에서
ADD df.sh /bin/df.sh 컨테이너 빌드시 호스트의 df.sh 파일을 컨테이너 /bin/df.sh로 복사 
RUN chmod +x /bin/df.sh /bin/df.sh에 권한 설정하는 chmod (change mode) 명령어로 +x 속성(executable/실행가능) 을 주는 명령어를 실행
ENTRYPOINT["/bin/df.sh"] /bin/df.sh 실행

⑥ 도커 이미지 빌드 및 확인

 


🚩 생성한 파일과 이미지를 가지고 컨테이너를 돌려 아래 구조로 연결

 

① index.html을 주기적으로 만드는 컨테이너 df 생성 및 실행

docker run -d -v /webdata:/webdata --name df df:latest

② 생성된 index.html을 클라이언트로 연결할 web server 컨테이너 web 생성 및 실행 

docker run -d --name web -v /webdata:/usr/share/nginx/html:ro -p 80:80 nginx:1.14

③ 결과

문제

📌 MySQL 로 풀이

 

📌 문제 링크 :

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

📌 문제 설명 :

보호소에서는 몇 시에 입양이 가장 활발하게 일어나는지 알아보려 합니다. 0시부터 23시까지, 각 시간대별로 입양이 몇 건이나 발생했는지 조회하는 SQL문을 작성해주세요. 이때 결과는 시간대 순으로 정렬해야 합니다.

 

📌 테이블 :

ANIMAL_OUTS 테이블은 동물 보호소에서 입양 보낸 동물의 정보를 담은 테이블입니다. ANIMAL_OUTS 테이블 구조는 다음과 같으며, ANIMAL_ID, ANIMAL_TYPE, DATETIME, NAME, SEX_UPON_OUTCOME는 각각 동물의 아이디, 생물 종, 입양일, 이름, 성별 및 중성화 여부를 나타냅니다.

ANIMAL_ID VARCHAR(N) FALSE
ANIMAL_TYPE VARCHAR(N) FALSE
DATETIME DATETIME FALSE
NAME VARCHAR(N) TRUE
SEX_UPON_OUTCOME VARCHAR(N) FALSE

 


풀이

쿼리) 

SET @HOUR = -1;
SELECT 
    (@HOUR := @HOUR + 1) AS HOUR,
    (SELECT 
        COUNT(*)
    FROM ANIMAL_OUTS 
    WHERE HOUR(DATETIME) = @HOUR) AS COUNT
FROM ANIMAL_OUTS
WHERE @HOUR < 23;

 

리뷰) 

이 문제에 대해서는 쿼리로 테이블을 만드는 방법을 모르겠어서 다른 사람의 풀이를 참조해서 공부했다

여기서 중요한건, 쿼리에서도 변수를 활용할 수 있다는 것!!!! 

 

사용하는 방식은 

SET @변수명 = 변수값; 

으로 @(앳사인)뒤에 변수명을 정해주고 해당 변수에 값을 선언하면 된다. 그리고 이후의 SELECT문과 구분되기 위해 ;(세미콜론)은 필수다. 

 

이렇게 선언하고나서 SELECT문에서 @변수명으로 사용이 가능하게되고

만약 SELECT문 안에서 변수에 값을 다시 대입해야 한다면 단순히 = (이퀄)만 사용하는 게 아니라 := (콜론 + 이퀄)을 해야 변한 값이 대입이 된다. 

 

그래서 이를 참고해서 아래 질의문을 파악해보면 요렇게 된다.

SET @HOUR = -1; 	# @HOUR이란 변수에 -1을 대입
SELECT 
    (@HOUR := @HOUR + 1) AS HOUR,	# @HOUR에 @HOUR에서 +1한 값을 다시 대입해서 0부터 출력
    (SELECT 
        COUNT(*)	# 7라인에 일치하는 시간 것만 조회하니 전체조회 COUNT하면 그시간대 입양된 수
    FROM ANIMAL_OUTS 
    WHERE HOUR(DATETIME) = @HOUR) AS COUNT	# 그리고 조건에서도 이 @변수와 일치하는 조건
FROM ANIMAL_OUTS
WHERE @HOUR < 23;	# 이 조건으로 @HOUR은 22까지지만 SELECT문 안에서 +1하는 구문때문에 23까지 출력

문제

📌 MySQL 로 풀이

 

📌 문제 링크 :

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

📌 문제 설명 :

USED_GOODS_BOARD와 USED_GOODS_FILE 테이블에서 조회수가 가장 높은 중고거래 게시물에 대한 첨부파일 경로를 조회하는 SQL문을 작성해주세요. 첨부파일 경로는 FILE ID를 기준으로 내림차순 정렬해주세요. 기본적인 파일경로는 /home/grep/src/ 이며, 게시글 ID를 기준으로 디렉토리가 구분되고, 파일이름은 파일 ID, 파일 이름, 파일 확장자로 구성되도록 출력해주세요. 조회수가 가장 높은 게시물은 하나만 존재합니다.

 

📌 테이블 :

다음은 중고거래 게시판 정보를 담은 USED_GOODS_BOARD 테이블과 중고거래 게시판 첨부파일 정보를 담은 USED_GOODS_FILE 테이블입니다. USED_GOODS_BOARD 테이블은 다음과 같으며 BOARD_ID, WRITER_ID, TITLE, CONTENTS, PRICE, CREATED_DATE, STATUS, VIEWS은 게시글 ID, 작성자 ID, 게시글 제목, 게시글 내용, 가격, 작성일, 거래상태, 조회수를 의미합니다.

BOARD_ID VARCHAR(5) FALSE
WRITER_ID VARCHAR(50) FALSE
TITLE VARCHAR(100) FALSE
CONTENTS VARCHAR(1000) FALSE
PRICE NUMBER FALSE
CREATED_DATE DATE FALSE
STATUS VARCHAR(10) FALSE
VIEWS NUMBER FALSE

USED_GOODS_FILE 테이블은 다음과 같으며 FILE_ID, FILE_EXT, FILE_NAME, BOARD_ID는 각각 파일 ID, 파일 확장자, 파일 이름, 게시글 ID를 의미합니다.

FILE_ID VARCHAR(10) FALSE
FILE_EXT VARCHAR(5) FALSE
FILE_NAME VARCHAR(256) FALSE
BOARD_ID VARCHAR(10) FALSE

 


풀이

추측) 

가장 조회수가 많이 나오는 글 ID를 먼저 골라오는 게 공수를 줄일 수 있을 듯 하다.

글이랑 첨부파일 먼저 매칭 시키면 일이 커지니까.

그래서 조회수 최대값을 조회하고 이에 해당하는 글 ID를 찾아 그에 해당하는 파일들을 찾는다.

그리고 FILE_PATH가 파일첨부 테이블의 컬럼들을 조합해서 파일경로로 만들어 출력하면 될듯하다. 

 

쿼리) 

* 1차) 서브쿼리 이용

SELECT CONCAT('/home/grep/src/', BOARD_ID, '/', FILE_ID, FILE_NAME, FILE_EXT) AS FILE_PATH
FROM USED_GOODS_FILE 
WHERE BOARD_ID IN (SELECT BOARD_ID
                    FROM USED_GOODS_BOARD
                    WHERE VIEWS IN (SELECT MAX(VIEWS)
                                    FROM USED_GOODS_BOARD))
ORDER BY FILE_ID DESC

* 2차) JOIN 이용

SELECT CONCAT('/home/grep/src/', BOARD.BOARD_ID, '/', FILE.FILE_ID, FILE.FILE_NAME, FILE.FILE_EXT) AS FILE_PATH
FROM USED_GOODS_FILE AS FILE
LEFT JOIN USED_GOODS_BOARD AS BOARD
ON FILE.BOARD_ID = BOARD.BOARD_ID
WHERE BOARD.VIEWS IN (SELECT MAX(VIEWS) 
               		  FROM USED_GOODS_BOARD)
ORDER BY FILE.FILE_ID DESC

 

 

리뷰) 

조금 궁금한게 서브쿼리 2번으로 필터링하는거랑..

BOARD랑 FILE이랑 매칭하고 WHERE로 최대값 걸러내면 뭐가 더 성능적으로 좋은걸까?

 

쿼리문 실행 순서가 아래와 같은 걸로 아는데 

from [ > on > join ] > where > group by [ > having ] > select > order by > limit

 

WHERE로 필터링 하기 전에 ON하고 JOIN해서 두 테이블을 매칭하면

서브쿼리로 조회글 아이디 하나 찾아오는 거 보다 더 공수가 들거 같은데.. 

 

둘다 실행계획이 왜 같음?!?!

 

라는 내생각과 다르게 찾아보니 충격적인 결과가!

 

서브쿼리의 성능적 문제는 서브쿼리와 컬럼을 비교할때마다 내부 쿼리가 실행된다는 점이다. (😧‼️)

쿼리를 반복해서 날리니 성능이 낮아질 수 밖에 없고,

그런 문제때문에 최근 MySQL은 사용자가 서브쿼리문을 사용하면 자체적으로 조인문으로 변환시켜 실행하도록 업데이트되어 결국 둘다 실행계획이 같았던 것. 내부적으로 변환해줘도 꼭 필요한 경우가 아니라면 서브쿼리는 남용하지 않는 것이 좋다고 한다. 주의하도록!!!

 


참고자료

 

[MYSQL] 📚 JOIN과 서브쿼리 차이 및 변환 💯 정리

조인(JOIN) vs 서브쿼리(Sub Query) 조인과 서브쿼리는 때로 동일한 결과를 얻을 수 있다. 상황에 따라 조인을 사용하는 것이 훨씬 좋을 때도 있고, 반면에 서브 쿼리를 사용하는 것이 좋을 때도 있다.

inpa.tistory.com

 

문제

📌 MySQL 로 풀이

 

📌 문제 링크 :

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

📌 문제 설명 :

CAR_RENTAL_COMPANY_RENTAL_HISTORY 테이블에서 대여 시작일을 기준으로 2022년 8월부터 2022년 10월까지 총 대여 횟수가 5회 이상인 자동차들에 대해서 해당 기간 동안의 월별 자동차 ID 별 총 대여 횟수(컬럼명: RECORDS) 리스트를 출력하는 SQL문을 작성해주세요. 결과는 월을 기준으로 오름차순 정렬하고, 월이 같다면 자동차 ID를 기준으로 내림차순 정렬해주세요. 특정 월의 총 대여 횟수가 0인 경우에는 결과에서 제외해주세요.

 

📌 테이블 :

다음은 어느 자동차 대여 회사의 자동차 대여 기록 정보를 담은 CAR_RENTAL_COMPANY_RENTAL_HISTORY 테이블입니다. CAR_RENTAL_COMPANY_RENTAL_HISTORY 테이블은 아래와 같은 구조로 되어있으며, HISTORY_ID, CAR_ID, START_DATE, END_DATE 는 각각 자동차 대여 기록 ID, 자동차 ID, 대여 시작일, 대여 종료일을 나타냅니다.

HISTORY_ID INTEGER FALSE
CAR_ID INTEGER FALSE
START_DATE DATE FALSE
END_DATE DATE FALSE

 


풀이

추측) 

예약한 날의 조건에 맞게 먼저 필터링해서 ROW수를 줄이고,

그 3개월 안에 5번 이상인 CAR_ID를 질의한 후에 

그 CAR_ID 대상으로 ROW수를 줄인 결과에서 해당 기간 동안을 달기준, 차기준으로 GROUP화 시켜 그룹 함수 카운트로달마다 몇번 렌트가 됬는지 질의하면 될 듯하다. 

 

쿼리) 

* 1차) 

WITH F AS 
(
SELECT HISTORY_ID, CAR_ID, MONTH(START_DATE) AS MONTH
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY 
WHERE DATE_FORMAT(START_DATE,'%Y-%m-%d') BETWEEN '2022-08-01' AND '2022-10-31'
)

SELECT MONTH, CAR_ID, COUNT(CAR_ID) AS RECORDS
FROM F
WHERE CAR_ID IN (SELECT CAR_ID 
                 FROM F 
                 GROUP BY CAR_ID
                 HAVING COUNT(CAR_ID) >= 5)
GROUP BY MONTH, CAR_ID
ORDER BY MONTH ASC, CAR_ID DESC

* 2차) 서브쿼리를 JOIN으로 교체

WITH F AS 
(
SELECT HISTORY_ID, CAR_ID, MONTH(START_DATE) AS MONTH
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY 
WHERE DATE_FORMAT(START_DATE,'%Y-%m-%d') BETWEEN '2022-08-01' AND '2022-10-31'
)

SELECT F.MONTH, F.CAR_ID, COUNT(F.CAR_ID) AS RECORDS
FROM F
INNER JOIN (SELECT CAR_ID 
            FROM F 
            GROUP BY CAR_ID
            HAVING COUNT(CAR_ID) >= 5) AS A
ON F.CAR_ID = A.CAR_ID
GROUP BY F.MONTH, F.CAR_ID
ORDER BY F.MONTH ASC, F.CAR_ID DESC

 

 

리뷰) 

하다 보니 "2022년 8월부터 2022년 10월까지 총 대여 횟수가 5회 이상인 자동차들에 대해서 해당 기간 동안의 월별 자동차 ID 별 총 대여 횟수(컬럼명: RECORDS) 리스트를 출력" 이분에서 8월부터 10월까지의 결과를 두번 써야할 일이 생겼다.

 

3개월간 총 5회 렌트된 차를 검색할 때 한번

3개월간 기록을 달마다 차마다 렌트 카운트 해줄 때 한번

 

그래서 이번엔 임시 테이블을 써서 2번 반복안하고 한번의 결과물로 SELECT문에 두번 쓰는 걸로!

📌 공부 계기

추가적인 도커 공부를 위해 유튜브 따배도 도커 시리즈를 보면서 정리해 봅니다. 

목차

 



📍7-1. 컨테이너 관리하기 : 이론

공식문서 링크

 

Docker run reference

 

docs.docker.com

 

 

1️⃣ 컨테이너 하드웨어 리소스 제한 어떻게 해요?

 

컨테이너는 호스트 하드웨어 리소스의 사용 제한을 받지 않는다. (호스트의 전부 사용가능)

효율적으로 사용하기 위해서 리소스 제한을 걸어야한다.

 

Docker 명령어를 통해 제한 할 수 있는 리소스

- CPU, Memory, Disk I/O 

 

$ docker run --help

명령어에서 그 종류를 확인 할 수 있다. 

 

  •  Memory 리소스 제한 

제한단위 b (바이트), k(키로바이트), m(메가바이트), g(기가바이트)로 할당

옵션 의미
--memory, -m 컨테이너가 사용할 최대 메모리 설정, 그 이상 쓰게되면 컨테이너가 스스로 킬함
--memory-swap 컨테이너의 스왑메모리[각주:1] 영역 설정
설정시 주의)
이 옵션뒤의 메모리 크기는 메모리 + 스왑메모리의 크기를 넣는다.
스왑메모리를 사용하지 않으려면 이 옵션뒤에 메모리 크기와 동일하게 적어주면 되며
만약 스왑 메모리를 설정을 따로 안하면 메모리의 2배가 설정된다.
--memory-reservation --memory 값보다 적은 값으로 구성하는 소프트 제한 값 설정 
--oom-kill-disable OOM Killer[각주:2]가 프로세스 kill하지 못하도록 보호
$ docker run -d -m 512m nginx:1.14
      메모리 512 메가 바이트로 설정  이미지명:태그
      -m 1g --memory-reservation 500m  
      해당 컨테이너는 최소 500메가 바이트를 보장받고 최대 1기가바이트 사용가능   
      -m 200m --memory-swap 300m  
      메모리는 200메가바이트 할당하고 스왑 뒤에 300메가 바이트를 써놨으니
200m + 300m = 500m 일까? ❌
뒤의 300m는 앞의 200m를 포함하고 있어 실제 스왑메모리는 100m에 해당
 
      -m 200m --oom-kill-disable  
      메모리는 200m 할당하고 물리메모리가 부족해서 oom killer가 실행됬을 때에도 이 컨테이너는 강제 종료되지 않고 실행을 보장받는다.   

 

 

  •  CPU 리소스 제한 
옵션 의미
--cpus 컨테이너에 할당할 최대 CPI core를 지정. 어떤 core를 쓸지는 지정되지 않는다
--cpuset-cpus 컨테이너가 사용할 수 있는 CPU나 코어를 할당. CPU index는 0부터 시작.
--cpu--share 컨테이너가 사용하는 CPU의 상대 가중치 설정.
말그대로 CPU를 상대적으로 얼마나 사용할 수 있는지를 설정한다. 기본은 1024이다.
$ docker run -d -cpus=".5" ubuntu:20.04
      이 컨테이너는 최대 0.5개의 CPU파워 사용가능 이미지명:태그
      -cpuset-cpus 0-3  
      이 컨테이너는 인덱스 0부터 3까지 해당하는 CPU가 할당  
      -cpu-shares 2048  
      컨테이너가 사용하는 CPU 상대 가중치를 2048로 설정
만약 다른 컨테이너가 1024라면 이 컨테이너는 다른 컨테이너에 비해 2배 사용가능
 

 

옵션 의미
--blkio-weight
--blkio-weight-device
모든 컨테이너가 동작시 block I/O에 I/O 스케쥴링을 받게 되는데 여기서 Quota(쿼터/몫)를 설정할 수 있으며 100-1000까지 선택가능하다
기본은  500
weight는 일단 상대적 가중치를 의미하며 만약 다른 컨테이너가 500이고 한 컨테이너만 1000일시 I/O 스케쥴링을 다른 컨테이너에 비해 2배더 할당 받을 수 있다. 
-devide가 붙은 옵션은 특정 디바이스에 대해서만 가중치를 부여한다. 
--device-read-bps
--device-write-bps
특정 디바이스에 대한 읽기와 쓰기 작업의 초당 제한은 kb, mb, gb 단위로 설정
--device-read-iops
--device-write-iops
컨테이너의 read/write 속도의 쿼터를 설정한다
초당 쿼터를 제한해서 I/O를 발생시킨다. 설정시 0 이상의 정수로 표기하며 이 수는 아래 식의 iops 값에 해당한다. 
초당 데이터 전송량 = IOPS * 블럭크기 (단위 데이터 용량)
$ docker run  -it --rm  --blkio-weight 100 ubuntu:latest /bin/bash
      (다른 컨테이너들이 전부 500이면) 이 컨테이너의 Block IO의 쿼터를 100으로 하여 500보다 상대적으로 적게 리소스를 할당 받겠다    
      --device-write-bps /dev/vda:1mb    
      디바이스 vda에 write해줄때는 최대 1메가바이트로 설정    
      --device-write-iops /dev/vda:100    
      디바이스 vda에 iops값 100을 할당    

 


2️⃣ 컨테이너 사용 리소스를 확인하는 모니터링 툴이 있나요?

 

  • 도커 모니터링 명령어

1) 실행중인 컨테이너의 런타임 통계 확인

docker stats [옵션] [컨테이너]

 

2) 도커 호스트의 실시간 event 정보 수집 및 출력

docker events -f container=<컨테이너명>
docker image -f container=<컨테이너명>

 

  • cAdvisor

구글에서 만든 도커 모니터링 툴

 

GitHub - google/cadvisor: Analyzes resource usage and performance characteristics of running containers.

Analyzes resource usage and performance characteristics of running containers. - GitHub - google/cadvisor: Analyzes resource usage and performance characteristics of running containers.

github.com

 

 


📍7-2. 컨테이너 관리하기 : 실습

 

➕ 이 실습 전에 추가로 알아둘 내용

리눅스의 부하 테스트 프로그램 중 하나 Stress

더보기

부하 테스트 프로그램 stress를 이용한 dockerfile 생성

비쥬얼 에디터로 dockerfile 생성

 

컨테이너 빌드하고 

 

아래 명령어를 치면 2개의 cpu에 작업부하를 발생시킴

stress --cpu 2

아래는 메모리 부하 테스트로 프로세스 2개와 사용할 메모리 만큼을 부하 발생시킴

stress --vm 2 --vm-bytes <사용할 크기>

 

1️⃣ 컨테이너 리소스 제한

  •  메모리 리소스 제한 
$ docke run [-m 메모리크기] [--memory-wap 메모리+스왑크기] <이미지명> [명령어]
# $ docke run -m 100m --memory-swap 100m stress:latest stress --vm 1 --vm-bytes 90m -t 5s
$ docker run -m 100m --memory-swap 100m stress:latest stress --vm 1 --vm-bytes 90m -t 5s
    물리메모리 물리메모리+스왑메모리 이미지 명령어 stress 90mb의 메모리 부하를 5초간

성공

100mb의 메모리를 90mb만 사용했으므로 성공적으로 실행 

하지만 만약 90mb가 아니라 150mb를 준다면?

실패

최대 메모리를 오버하여 실패가 되어 바로 컨테이너가 kill된걸 확인할 수 있다.

 

여기서 스왑메모리 옵션을 지워준다면?

성공!

왜냐하면 스왑메모리를 명령어로 설정해주지 않으면 물리메모리의 2배로 기본 설정이 되기 때문에 

--memory-swap 200m를 설정해준 것처럼 되어 정상 작동한다. 

 

🚩 트러블 (해결은 아직이고 일단 체크)

더보기

또잉?? oom killer로 테스트 해보려고 하니 당신의 kernel에서 지원을 안해 oom killer 내용을 삭제했다는 메시지가 나온다.

docker run -d -m 100m --name m4 --oom-kill-disable=true nginx
inspect로 봐도 안되어 있음

 

ubuntu 전체가 그런건 아닌거 같고 .. 검색해보니 현재 나랑 같은 22.04 LTS에서 이런 문제를 겪었다는 글을 찾을 수 있었다. 원인과 해결책은 아직이라 좀 더 조사해봐야겠다.

 

 


 일단 20.04 LTS에서는 정상작동하는 것 확인

docker inspect --format '{{.HostConfig.OomKillDisable}}' m4

 

또 다른 파일을 조회하여 확인 가능하다. 

cat /sys/fs/cgroup/memory/docker/컨테이너fullID/memory.oom_control

 

한데 이것도 22.04에서 시스템 디렉토리 구조가 바뀌었고, 컨테이너에 memory.oom_control이라는 파일도 없다. 

 


  •  CPU 리소스 제한 실습 

1) cpu 확인

lscpu

버추얼박스에서 cpu2개 설정해놨기 때문에 2개 조회

 

2) CPU 할당해보기

인덱스 1인 CPU 할당하고(--cpuset-cpus 1), stress 이미지에 cpu가 2개 돌아가게 되어있어서 cpu를 1개도 바꿔주고(stress --cpu 1), 이름 c1이라는 컨테이너를 생성해(--name c1) 백그라운드(-d)로 러닝시키는 명령어 

# docker run [--cpuset-cpus cpu인덱스] [--name 컨테이너이름] [-d] <이미지명:태그> [명령어]
docker run --cpuset-cpus 1 --name c1 -d stress:latest stress --cpu 1

 

3) 확인

htop
# 이 명령어 쓰려면 아래 명령어로 htop 설치해야함
# sudo apt install htop

보면 프로세스로 stress --cpu 1이 실행되는 걸 확인할 수 있고, cpu 1번이 100퍼센트로 돌고 있는 걸 확인할 수 있다. 

으로 0번도 100퍼센트 돌아가게 하니

아까의 컨테이너랑 같이 0번과 1번 모두 100프로 돌고 있다. 

 

만약 --cpuser-cpus를 0부터 1까지로 설정하고 cpu 실행을 1개로만 설정했다면 0부터 1사이에서 그때그때 상황마다 1개만 선택해서 돌아간다.

docker run --cpuset-cpus 0-1 --name c1 -d stress:latest stress --cpu 1

 

4) cpu-share로 가중치를 줘보자

각각 cload1에 2048, cload2에 기본, cload3,4에 512 가중치를 주었다. 

# docker run [-c 가중치] [--name 컨테이너명] [-d] <이미지명:태그>

# 가중치 설정 케이스
docker run -c 2048 --name cload1 -d stress:latest
# 가중치 설정 따로 안한 기본 케이스
docker run --name cload2 -d stress:latest
# 가중치 설정 케이스
docker run -c 512 --name cload3 -d stress:latest
# 가중치 설정 케이스 (3이랑 똑같이)
docker run -c 512 --name cload4 -d stress:latest

이거 실제 가중치를 받은 만큼 알아보고 싶다!

그럼 모니터링 툴로 ㄱㄱ

 

 


  •  Block I/O 제한 

1) block I/O 조회

lsblk

하늘색 부분이 device 이름이다

 

2) 디바이스 별 iops 설정

# docker run [-it] [--rm] [--device-write-iops /dev/디바이스이름:쿼터] <이미지명:태그> [명령어]
docker run -it --rm --device-write-iops /dev/sda:10 ubuntu:latest /bin/bash

여기서 --rm 은 컨테이너를 일회성으로 쓸때 사용하며, 컨테이너가 종료되면 컨테이너와 컨테이너 관련 리소스까지 깨끗하게 삭제하는 옵션이다. 

 

위의 명령어로  쿼터 10으로 할당된 우분투를 실행시킨 후

아래 명령어를 실행해보자

dd if=/dev/zero of=file1 bs=1M count=10 oflag=direct

* dd : data duplicator

* if : 지정한 파일(위에서는 /dev/zero)를 입력 대상으로 설정

* of : 지정한 파일(file1)을 출력 대상으로 설정

* bs : 바이트를 기준으로 하며 한번에 읽고 쓸 최대 바이트 크기 지정 

* count : 지정한 블록 수 만큼 복사

* oflag : 테스트 파일을 쓸때 사용하는 file flag를 설정, direct 경우 O_DIRECT플래그를 켜고 write()를 호출하게 되는데 이 플러그를 이용하면 파일시스템에 캐시 영역을 사용하지 않고 바로 디스크를 쓰기때문에 입출력 성능 측정할때 필요하다.[각주:4]

10개 복사한 결과와 걸린 시간등을 알 수 있다.

쿼터를 100으로 할당해서 다시 해보면

쿼터 10일때 5.5 MB/s 밖에 안나오던게 쿼터 100이 되니 93.2MB/s 나 나온걸 확인 할 수 있다. 

 

 


2️⃣ 컨테이너 모니터링하기

 

1) 컨테이너 런타임 통계

컨테이너 설정 안하면 실행중인 모든 컨테이너 대상 조회

docker stats [옵션] [컨테이너]

이렇게 런타임으로 돌아가는 걸 볼 수 있다

그런데 상대적 비율로 돌아간다고 하셨는데 CPU 비율이 가중치처럼 안돌아가는 데.. 왜지.. 오히려 기본설정인 cload2가 더 많은 비율을 보인다. 

 

cpu를 하나로 고정해봐도... cload2가 더 많은 퍼센트를 차지.. 기본 설정이 뭔가 달라진걸까?

확실히 기본 설정이 달라진 듯

cload5로 가중치를 1024를 명시해주고 돌려보니 확실히 맞는 비율이 나온다. 

 

다시 cpu를 하나로 고정하지말고 테스트 해보자

cpu를 여러개 사용하면 하나로 고정했을때 보다 안정정으로 비율이 나오지 않는다. 그래도 꽤 비율이 맞는 편

 

 


3️⃣ cAdvisor 설치해서 사용하기

 

그리고 cAdvisor 깃헙의 명령어를 적용

 

명령어 적용해 돌리고 해당 포트로 연결하면 아래와 같은 UI로 컨테이너를 모니터링 할 수 있다. 

  1.  Swap Memory
    RAM 즉 물리 메모리가 다 차게 되어 프로세스가 작업을 이어 나가지 못하고 종료되는 것을 방지하기 위해 하드디스크 공간을 이용하여 부족한 메모리를 대체할 수 있는 메모리 [본문으로]
  2. OOM Killer (Out Of Memory Killer)
    리눅스 기능 중 하나로 메모리가 부족할 경우 특정 프로세스를 강제로 종료하여 메모리를 확보하는 기능 [본문으로]
  3. Block I/O
    블록 장치는 개별 바이트 단위가 아닌 일정 크기(block) 단위로 접근하는 장치
    간단히 말하면 하드 디스크와 같은 대용량 저장 장치를 말한다. [본문으로]
  4. 출처: https://hbase.tistory.com/21 [본문으로]

+ Recent posts