본 포스팅은 데이터 중심 애플리케이션 설계의 7장 트랜잭션 단원을 참조하여 작성되었습니다.

또한 포스팅 내용 중 개인적인 의견이 들어간 부분도 있으니 자세한 내용은 책을 참조해주세요.

 

아무리 대단한 데이터 시스템이라도 언제나 아래와 같은 문제 상황이 발생할 수 있다.

- 데이터베이스의 소프트웨어 또는 하드웨어의 실패

- 예상치 못한 애플리케이션의 종료

- 네트워크 단절

- 여러 클라이언트의 동시 쓰기에 의한 덮어쓰기

- 데이터의 부분 갱신

- 클라이언트의 Race Condition에 의한 버그

 

뛰어난 시스템은 위와 같은 결함을 처리해서 전체적으로 시스템 장애로 이어지는 것을 막아야 한다.

이러한 결함을 다루는 기술은 트랜잭션이라는 매커니즘으로 사용되어 왔다.

 

트랜잭션은 애플리케이션의 몇 개의 읽기와 쓰기를 하나의 논리적 단위로 묶는 방법이다.

Transaction

Application에서 발생한 많은 명령을 하나의 Transaction으로 묶어 Database에 전달하고

Database는 Transaction의 모든 명령이 실행될 수 있는지 확인하고 성공한다면 Commit을 진행하고

실패한다면 기존에 실행된 Transaction 명령을 Rollback하고 Application에 Abort를 반환한다.

 

이는 Applicatoin의 Database에서 발생할 수 있는 다양한 문제를 Transaction이라는 개념을 통해 단순화 한 것이다.

Transaction은 수치적으로 정해진 것이 아니기 때문에 각 Application의 특성에 맞게 조절하는 것이 중요하다.

 

그렇다면 Application이 어느정도의 Transaction Level이 필요한지 어떻게 확인할 수 있을까?

이를 위해선 Transaction Level에 따른 제공 기능을 확인해보고 결정할 수 있을 것이다.

주로 연관된 문제는 동시성 문제이며 일반적으로 Database는 아래의 3가지 정도의 Transaction Level을 제공한다.

 

  • Read committed (커밋 후 읽기)
  • Snapshot Isolation (스냅샷 격리)
  • Serializability (직렬성)

Transaction을 지원하는 다양한 Database는 ACID라는 조건을 만족한다.

여기서 ACID는 원자성(Atomicity), 일관성(Consistency), 격리성(Isolation), 지속성(Duration)을 의미한다.

 

원자성이란 위에서 봤던 여러 명령을 가진 Transaction이 성공하면 commit되고 실패하면 전체가 rollback되는 것을 말한다.

즉, 하나의 Transaction은 쪼갤 수 없는 단위가 된다.

일관성은 데이터는 항상 진실이어야 한다는 개념이다.

다소 모호한 개념인데, 이는 Application에서 의미하는 데이터의 성질과 관계에 따른 일관성 보존을 의미한다.

예를들어 계좌 송금 시스템에서 송금된 금액은 반드시 목적지 계좌에 추가되며 송금된 계좌에서는 금액이 차감되는 일관성이 유지되어야 한다.

격리성은 동시에 실행되는 Transaction은 서로 격리되어야 하며 서로를 방해할 수 없다를 의미한다.

격리성을 보장하기 위한 방법은 다양한데 이는 Transaction Level에 따라 다르게 구현된다.

지속성은 Database에 저장된 데이터는 손실되지 않는 안전한 곳에 저장되어야 한다는 의미다.

 

ACID의 성질 중 Transaction과 직접적으로 연관되어 있는 것은 원자성과 격리성이다.

따라서 이 2개의 성질에 대해서 보장하는 방식을 알아보도록 하자.

 

문제점 1 - Dirty Read

특정 시간에 오픈되는 공연 티켓 예매 서비스가 있다고 생각해보자.

사용자는 관람하고 싶은 좌석 등급을 고르고 세부 좌석을 선택하고 금액을 지불해서 예매를 진행한다.

티켓 예매가 오픈되면 순간적인 트래픽이 급증하기 때문에 등급마다 남은 좌석 수를 별개의 테이블에 저장하고

사용자가 좌석 등급을 고를 때 해당 테이블에서 좌석 수를 읽어 보여준다.

 

그러면 특정 사용자가 좌석 예매를 완료했다고 하면 발생해야 하는 UPDATE는 2개가 된다.

UPDATE seats SET booked = true WHERE grade=S AND row=10 AND col=10
UPDATE seats_count SET count = count+1 WHERE grade=S

 

만약 첫번째 UPDATE가 실행되고 두번째 UPDATE가 실행되기 전에 다른 사용자가 S 등급의 좌석 수 조회를 요청한다면

실제 예매 가능한 좌석수와 조회되는 좌석 수는 다르게 나올 것이다.

이러한 현상을 격리성 위반, Dirty Read라고 한다.

즉, UPDATE를 2개 실행해야 하는 트랜잭션 A가 완료되기 전에 조회하는 트랜잭션 B가 커밋되지 않은 트랜잭션 A의 중간 결과를 읽을 수 있게 된 것이다.

 

문제점 2 - Dirty Write

두 사용자가 동일한 좌석을 예매하고 금액을 지불할 경우를 생각해보자.

# A 사용자
UPDATE seats SET booked = true WHERE grade=S AND row=10 AND col=10 AND booked=false # 1
UPDATE payment SET buyer='A' WHERE grade=S AND row=10 AND col=10 # 2

# B 사용자
UPDATE seats SET booked = true WHERE grade=S AND row=10 AND col=10 AND booked=false # 3
UPDATE payment SET buyer='B' WHERE grade=S AND row=10 AND col=10 # 4

 

 

굉장히 드문 경우겠지만, 두 사용자가 동시에 UPDATE seats를 했다면 두 사용자 모두 금액을 지불 할 수 있게 된다.

이렇게 되면 마지막에 금액을 지불한 사용자가 좌석의 buyer가 된다.

위처럼 seats table을 갱신하기 전에 다른 사용자 역시 갱신 요청을 하게 된다면 큰 문제를 유발시킬 수 있다.

문제 발생의 원인은 예매하려는 좌석에 대해 잠금을 하지 못해서 발생한 것으로 Dirty Write 문제라고 한다.

 

해결 방법 1 - Read Committed (커밋 후 읽기)

Drity Write  문제는 사실 MySQL과 같은 일반적인 RDBMS를 사용한다면 거의 겪지 않을 것이다.

RDBMS는 기본적으로 데이터베이스의 Row 수준의 잠금을 지원하기 때문이다.

따라서 트랜잭션이 특정 Row를 변경하고 싶다면 트랜잭션은 잠금을 획득해야하고 commit 또는 abort가 일어난 후에 다른 트랜잭션에서 해당 Row를 변경할 수 있게 된다.

그렇다면 Dirty Read의 경우는 어떨까?

어떤 트랜잭션이 변경을 위해 Row 잠금을 획득한 상태라면, 해당 Row를 읽기를 원하는 트랜잭션은 변경이 끝날 때까지 기다려야 할까?

대부분의 데이터베이스는 변경이 끝날때까지 기다리지 않고 변경 트랜잭션이 잠금을 획득하기 전에 있던 Row 값을 읽어 간다.

시간 Row의 현재 값 액션
00:01 X = 5 Transaction A : Row 잠금 획득
00:02   Transaction A : X = 6 변경
00:03   Transaction B : Row 읽기 X = 5
00:04 X = 6 Transaction A : X = 6 쓰기
00:05   Transactino A Row 잠금 해제

위와 같이 Transaction A가 변경을 위해 잠금을 획득하고 업데이트 중(00:01~00:05)이라고 하더라도 Transaction B는 00:03 시간에 X = 5인 변경 전의 값을 읽을 수 있다.

 

문제점 3 - Nonrepeatable Read

Read Committed가 보장되는 환경에서 사용자가 계좌 B(잔고 = 500원)에서 계좌 A(잔고 = 500원)로 돈(100원)을 전달하는 예제를 살펴보자.

시간 액션 결과
00:01 계좌 B에서 계좌 A로 100원 전달 요청
(계좌 A +100)
(계좌 B -100)
 
00:02 계좌 A 조회 A 계좌 잔고 = 500원
00:03 전달 완료  
00:04 계좌 B 조회 B  계좌 잔고 = 400원

Read Committed가 보장되기 때문에 계좌 B에서 계좌 A로 100원을 전달(00:01~00:03)하는 트랜잭션은 원자성과 격리성이 보장된다.

하지만 전달이 완료(트랜잭션 완료, 00:03)되기 전에 A 계좌를 조회(00:02)했다면 A 계좌는 전달되기 전의 잔고인 500원을 반환한다.

반면에 전달이 완료된 후 B 계좌를 조회(00:04)했다면 B 계좌는 전달된 후의 잔고인 400원을 반환한다.

이런 상황이 발생한다면 사용자는 1000원이 있지만 동시성 버그에 의해 900원이 있다고 조회될 것이다.

이러한 문제를 nonrepeatable read(비반복 읽기)라고 한다.

 

해결 방법 2 - Snapshot Isolation(Repeatable Read)

Snapshot Isolation은 nonrepeatable read를 해결할 수 있는 해결책이 된다.

각 트랜잭션은 데이터베이스의 일관된 Snapshot으로부터 데이터를 읽는다.

즉, 생성된 트랜잭션은 생성 당시의 데이터베이스에 commit되어 있는 현재의 모든 데이터를 본다.

따라서 트랜잭션 이후에 생성된 미래 데이터에 대해서는 볼 수 없고, 오직 과거에 커밋된 데이터만을 바라본다.

 

위의 nonrepeatable read의 문제였던 상황을 다시 확인해보자.

시간 트랜잭션 ID 액션 결과
00:01 1 계좌 B에서 계좌 A로 100원 전달 요청
(계좌 A +100)
(계좌 B -100)
 
00:02   트랜잭션 2 생성  
00:03 2 계좌 A 조회 A 계좌 잔고 = 500원
00:04   전달 완료  
00:05 2 계좌 B 조회 B  계좌 잔고 = 500원
00:06   트랜잭션 3 생성  
00:07 3 계좌 A 조회 A 계좌 잔고 = 600원
00:08 3 계좌 B 조회 B 계좌 잔고 = 400원

트랜잭션 2가 생성된 순간(00:02)은 아직 전달이 완료(commit)되지 않은 상태이기 때문에

각 계좌의 잔고를 조회(00:03, 00:05)했을 때 전달 전의 상태인 500원씩을 가지고 있다.

반면에 전달이 완료된 후 만들어진 트랜잭션 3은 전달이 완료된 금액인 600원/400원(00:07, 00:08)을 가진 것을 볼 수 있다.

이 같이 데이터베이스가 객체의 여러 버전을 함께 유지하는 기법을 다중 버전 동시성 제어(multi version concurrency control, MVCC)라고 한다. 

 

문제점 4 - Lost Update

Lost Update(갱신 손실) 문제는 주로 read-modify-write 주기를 가졌을 때 발생한다.

# read
SELECT value from counters WHERE key = 'foo';

# modify
int new_value = value+1;

# write
UPDATE counters SET value = new_value WHERE key = 'foo';

 

트랜잭션이 row의 특정 column을 증가시킬 때 기존 값을 읽고(read), 증가시키고(modify), 증가된 값을 쓴다(write).

이 때, 2개의 트랜잭션이 동시에 read-modify-write를 한다면 같은 값에 대해 수행하기 때문에 실제로 2만큼이 증가되지 않고 1만 증가된다.

 

이러한 문제는 대부분의 관계형 데이터베이스에서 원자적 쓰기 연산을 제공하는데 이를 통해 해결할 수 있다.

UPDATE counters SET value = value+1 WHERE key = 'foo';

 

또한 명시적 잠금을 이용해 값을 업데이트하는 트랜잭션이 해당 row를 잠금해 다른 트랜잭션이 읽지 못하도록 만들 수 있다.

BEGIN TRANSACTION;

SELECT * FROM object WHERE key = 'foo' FOR UPDATE; # 명시적 잠금

UPDATE object SET value = 'update' WHERE key = 'foo';

COMMIT;

 

문제점 5 - Write Skew & Phantom Read

Dirty Write와 Lost Update는 서로 다른 트랜잭션이 같은 객체에 동시에 쓰려고 할 때 발생한 문제점들이다.

Write Skew는 쓰기에서 문제가 발생한다는 점은 같지만 서로 다른 객체에 쓸 경우 발생하는 문제점이다.

다른 트랜잭션이 다른 객체에 쓰는게 어떤 문제가 되는걸까? 아래의 예를 살펴보자.

 

Tom은 쇼핑몰에서 포인트를 이용해서 물건을 사려고 한다.

쇼핑몰은 사용자의 포인트가 물품의 가격보다 크면 물품을 구매하는 order table에 이를 업데이트한다.

BEGIN TRANSACTION;

SELECT price FROM products WHERE product_id = 'foo';
SELECT point FROM points WHERE user_id = 'tom';

if (point >= price) {
    INSERT INTO order (user_id, product_id) VALUES ('tom', 'foo');
    UPDATE points SET point=point-price WHERE user_id = 'tom';
}

COMMIT;

 

만약 Tom이 2개의 트랜잭션을 이용해 'foo' product와 'bar' product을 동시에 포인트로 구매하려고 하면 어떻게 될까?

Snapshot Isoloation(Repeatable Read)이라면 'foo'를 구매하는 트랜잭션이 진행 중이더라도

동시에 생성된 트랜잭션의 snapshot에서 point는 차감이 안되었기 때문에 'bar' 물품 역시 구매가 되고 point는 음의 값을 가지게 될 수 있다.

Read Committed도 역시 물품 구매 중 값을 읽을 수 있기 때문에 Snapshot Isolation과 같은 문제점이 발생한다.

 

동시에 같은 객체에 쓰기가 아니기 때문에 원자성이나 격리성에 위배되지 않지만 분명한 문제가 발생한다.

이러한 문제를 해결하는 가장 좋은 방법은 한번에 하나의 트랜잭션만 실행하는 것이다.

뒤에서 나올 Serializability 수준을 사용하면 가능하지만 처리 속도의 이유로 이를 사용할 수 없다면 앞에 Lost Update의 해결 방법 중 하나였던 명시적 잠금을 이용해 해결할 수 있다.

BEGIN TRANSACTION;

SELECT price FROM products WHERE product_id = 'foo';
SELECT point FROM points WHERE user_id = 'tom' FOR UPDATE;

if (point >= price) {
    INSERT INTO order (user_id, product_id) VALUES ('tom', 'foo');
    UPDATE points SET point=point-price WHERE user_id = 'tom';
}

COMMIT;

 FOR UPDATE 를 사용해 points 테이블에서 user_id = 'tom'에 해당하는 row를 잠금하게 되면 해당 트랜잭션이 종료되기 전까지 다른 트랜잭션에서는 접근할 수 없게된다.

 

위와 같은 SELECT -> 조건 확인 -> UPDATE/INSERT와 같은 흐름에서 SELECT 부분에서 문제가 발생하는 경우를 Phantom Read라고 한다.

 

문제점 6 - Materializing Conflict (충돌 구체화)

위 Write Skew & Phantom Read는 명시적 잠금을 이용해 문제를 해결했다.

해결이 가능한 이유는 tom의 포인트 row와 같이 명시적 잠금을 할 대상이 정해져있기 때문이다.

하지만 조건 확인을 위한 SELECT에서 잠금할 대상이 없다면 Materializing Conflict(충돌 구체화) 문제가 발생한다.

예시를 위해 회의실 예약 시스템을 생각해보자.

BEGIN TRANSACTION;

SELECT count(*) FROM bookings 
WHERE start_time < '2020-07-31 14:00' 
AND end_time > '2020-07-31 15:00';

if (count == 0) {
    INSERT INTO bookings (start_time, end_time) VALUES ('2020-07-31 14:00', '2020-07-31 15:00');
}

COMMIT;

14:00에서 15:00 사이에 회의가 없다면 회의실을 예약한다.

조건 확인을 위해 조회한 값은 예약 시간에 존재하는 예약 정보인데 이를 잠금하는건 충돌 문제를 해결하는데 도움이 되지 않는다.

회의실 예약을 위해서는 조회된 값이 없어야 하고 이 때, 예약을 위해 Write를 진행한다.

하지만 Write 중에 다른 트랜잭션에서 동일한 내용이 실행됐다면 그 어떤 잠금도 없기 때문에 충돌이 발생한다.

 

이러한 경우에는 Serializability(직렬성) 수준의 트랜잭션을 이용해 한 순간에 하나의 트랜잭션만 실행되도록 해야한다.

 

해결 방법 3 - Serializability(직렬성)

동시성 문제를 해결하는 가장 좋은 방법은 동시성을 없애는 것이다.

Serializability는 동시성을 없애고 병렬적으로 트랜잭션이 들어와도 한 순간에 오직 하나의 트랜잭션만 처리한다.

이렇게 되면 앞에서 발생했던 모든 문제를 해결 할 수 있다.

 

직렬성을 이용할 때는 트랜잭션에서 실행해야 하는 구문을 모두 Database에 미리 생성해 놓는 stored procedure를 주로 사용한다.

 

직렬성의 장점 :

- 동시성 문제(Dirty Write, Lost Update, Wirte Skew, Materializing Conflict)가 발생하지 않는다.

 

직렬성의 단점 :

- 하나의 thread만 이용하기 때문에 Database 단일 코어의 성능이 중요해진다.

- 하나의 트랜잭션만 처리하기 때문에 시간 당 처리량이 낮아진다.

 

정리

Database에서 지원하는 트랜잭션의 수준(Level)에 대해 알아봤다.

몇몇 NoSQL은 이러한 트랜잭션을 포기하고 높은 처리 속도와 분산 저장을 선택한 경우도 많다.

어떤 트랜잭션 수준을 사용하든 또는 아예 사용하지 않든 중요한 것은 애플리케이션에서 필요로 하는 정도의 수준에서

Best Practice를 파악하고 적용하는 것 같다.

 

TPC - The Transaction Processing Performance Council

 

TPC는 OLTP database의 성능 측정(benchmark)를 위해 만들어졌고, TPC-C는 이 중 복잡한 트랜잭션을 처리하는 상황을 시뮬레이션 할 수 있는 database의 table 구조

 

TPC-C table 구조

https://programmer.help/blogs/tpcc-instructions.html

 

테이블의 이름에서 확인 할 수 있듯이 일반적인 e-commerce 서비스 table 구조와 유사한 형태를 가지고 있다.

 

위 테이블에 많은 데이터를 집어 넣으면서 성능을 측정 할 수 있는데 생성할 트랜잭션의 갯수 역시 정해져있다.

 

Warehouse(W)에 따른 생성되는 데이터의 갯수

https://programmer.help/blogs/tpcc-instructions.html

창고(Warehouse) 하나가 생성되면 약 30,000개의 주문(Order)가 생기는 식으로 계산할 수 있다.

 

tpcc-mysql 을 이용해 MySQL에 Warehouse가 한 개인 데이터를 생성해보자.

mac을 이용해서 tpcc-mysql를 빌드했을 때 에러가 발생하여 docker-compose를 이용해 테스트를 진행했다.

 

tpcc-mysql docker image 만들기

$ git clone https://github.com/Percona-Lab/tpcc-mysql.git
$ cd tpcc-mysql
$ docker build . -t tpcc-mysql
$ docker images
...
tpcc-mysql              latest
...

 

docker-compose를 이용해 mysql과 tpcc-mysql 실행하기

 

docker-compose.yml

version: "3"
services:
    mysql:
        image: mysql:5.7
        environment:
          MYSQL_DATABASE: tpcc
          MYSQL_ROOT_PASSWORD: root
          MYSQL_ROOT_HOST: '%'
        ports:
          - 3306:3306
        volumes:
          - mysql_volume:/var/lib/mysql
    tpcc:
      image: tpcc-mysql:latest
      command: tail -f /dev/null
      links:
      - mysql

volumes:
    mysql_volume:

mysql container가 재시작 된 이후에도 데이터를 보존하기 위해 docker volume을 사용했다.

$ docker-compose up -d
Creating network "tpcc-mysql_default" with the default driver
Creating tpcc-mysql_mysql_1 ... done
Creating tpcc-mysql_tpcc_1  ... done
$ docker ps
IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES
eb2faaa6072f        tpcc-mysql:latest   "tail -f /dev/null"      13 seconds ago      Up 12 seconds                                           tpcc-mysql_tpcc_1
9ecafd85f8bb        mysql:5.7           "docker-entrypoint.s…"   14 seconds ago      Up 12 seconds       0.0.0.0:3306->3306/tcp, 33060/tcp   tpcc-mysql_mysql_1

docker-compose를 통해 mysql과 tpcc-mysql 을 실행했다면 tpcc-mysql으로 들어가 benchmark 작업을 수행할 수 있다.

$ docker exec -it <tpcc-mysql Container ID> bash
$ root@eb2faaa6072f:/tpcc-mysql#

이제 Database와 Table을 만들어 보자.

# tpcc1 database 생성
$ mysqladmin create tpcc1 -hmysql -uroot -proot
mysqladmin: [Warning] Using a password on the command line interface can be insecure.

# tpcc1에 table 생성
$ mysql tpcc1 -hmysql -uroot -proot < create_table.sql
mysql: [Warning] Using a password on the command line interface can be insecure.

docker exec 를 통해 tpcc-mysql cotainer에 접속했듯이 mysql container에 접속하면 생성된 table을 확인 할 수 있다.

mysql> use tpcc1;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed

mysql> show tables;
+-----------------+
| Tables_in_tpcc1 |
+-----------------+
| customer        |
| district        |
| history         |
| item            |
| new_orders      |
| order_line      |
| orders          |
| stock           |
| warehouse       |
+-----------------+

table의 foreign key와 index에 관한 정보를 추가하고 싶다면 아래 명령어를 추가로 실행하면 된다.

아래 명령어는 데이터가 생성된 이후에 진행해도 무관하다.

$ mysql tpcc1 -hmysql -uroot -proot < add_fkey_idx.sql

데이터를 생성하기 위한 table이 설정이 완료되었고, benchmark를 위한 초기 데이터를 생성해보자.

 

warehouse의 개수는 1개(-w1)로 설정했다.

$ ./tpcc_load -hmysql -dtpcc1 -uroot -proot -w1
*************************************
*** TPCC-mysql Data Loader        ***
*************************************
option h with value 'mysql'
option d with value 'tpcc1'
option u with value 'root'
option p with value 'root'
option w with value '1'
<Parameters>
     [server]: mysql
     [port]: 3306
     [DBname]: tpcc1
       [user]: root
       [pass]: root
  [warehouse]: 1
TPCC Data Load Started...
Loading Item
.................................................. 5000
...
...DATA LOADING COMPLETED SUCCESSFULLY.

제대로 데이터가 생성되었는지 확인하기 위해 mysql을 통해 확인할 수 있다.

mysql> select count(*) from warehouse;
+----------+
| count(*) |
+----------+
|        1 |
+----------+
1 row in set (0.00 sec)

mysql> select count(*) from orders;
+----------+
| count(*) |
+----------+
|    30000 |
+----------+
1 row in set (0.01 sec)

warehouse를 1개 생성했기 때문에 30K*W 개수만큼 생성되는 orders는 30,000개가 생성된걸 확인할 수 있다.

 

이제 생성된 데이터를 기반으로 benchmark 실행해보자.

$ ./tpcc_start -hmysql -P3306 -dtpcc1 -uroot -proot -w1 -c32 -r10 -l100
***************************************
*** ###easy### TPC-C Load Generator ***
***************************************
option h with value 'mysql'
option P with value '3306'
option d with value 'tpcc1'
option u with value 'root'
option p with value 'root'
option w with value '1'
option c with value '32'
option r with value '10'
option l with value '100'
<Parameters>
     [server]: mysql
     [port]: 3306
     [DBname]: tpcc1
       [user]: root
       [pass]: root
  [warehouse]: 1
 [connection]: 32
     [rampup]: 10 (sec.)
    [measure]: 100 (sec.)

RAMP-UP TIME.(10 sec.)

MEASURING START.

  10, trx: 1466, 95%: 45.707, 99%: 66.588, max_rt: 134.603, 1463|259.705, 147|48.093, 147|117.481, 146|234.267
  20, trx: 1491, 95%: 44.921, 99%: 60.163, max_rt: 80.195, 1491|258.610, 149|25.793, 149|110.530, 151|144.989
  30, trx: 1476, 95%: 45.394, 99%: 62.963, max_rt: 91.697, 1473|257.982, 147|29.405, 148|101.045, 147|134.593
  40, trx: 1487, 95%: 42.424, 99%: 57.919, max_rt: 76.369, 1486|251.475, 149|38.944, 148|102.436, 149|165.392
  50, trx: 1523, 95%: 42.488, 99%: 57.797, max_rt: 74.825, 1533|246.987, 152|38.309, 153|110.212, 151|174.092
  60, trx: 1508, 95%: 43.661, 99%: 60.760, max_rt: 114.722, 1498|244.906, 151|52.633, 151|147.776, 152|150.247
  70, trx: 1479, 95%: 45.996, 99%: 57.521, max_rt: 81.752, 1483|268.676, 148|45.097, 147|118.265, 147|168.063

-w : benchmark에 사용할 warehouse의 개수

-c : benchmark에 사용할 database connection의 개수

-r : bechmark 테스트 주기 (Ramp-Up Time)

-l : benchmark의 Runnig Time

 

측정 결과를 간단하게 해석해보면 아래와 같다.

처음 10초 동안 1466개의 New Order트랜잭션이 발생

New Order 트랜잭션 중 95%에 해당하는 트랜잭션의 소요시간이 45.707 초, 99%가 66.588초가 걸림

가장 오래 걸린 New Order 트랜잭션 시간은 134.603 초가 걸림

New Order 외의 트랜잭션에 대해서 throughput과 max response 시간은 각각

1463|259.705, 147|48.093, 147|117.481, 146|234.267 이 됨

 

TPC benchmark는 OLTP 데이터베이스의 성능을 측정하는게 주된 목표이지만

앞으로 MySQL에 대해 공부하면서 주로 사용할 것 같아 정리해봤다.

 

mysql 공식페이지에서는 이러한 benchmark를 사용자가 직접 커스텀할 수 있는 툴인 mysqlslap을 제공하고 있다.

https://dev.mysql.com/doc/refman/5.7/en/custom-benchmarks.html

 

 

 

 

본 포스팅은 Udemy Certificated Kubernetes Administrator 강좌를 공부한 내용입니다.

 

Certified Kubernetes Administrator (CKA) Practice Exam Tests

Prepare for the Certified Kubernetes Administrators Certification with live practice tests right in your browser - CKA

www.udemy.com

Kubernetes Upgrade

Kubernetes 역시 소프트웨어이기 때문에 버전이 존재하고 하위 버전을 호환하기 위한 backward compatibility를 갖추고 있어야 한다.

Kubernetes는 Master Node에서 동작하는 ControlPlane의 구성요소와

Worker Node에서 동작하는 kubelet, kube-proxy,

사용자가 사용하는 kubectl 등 다양한 프로세스로 구성되어 있다.

 

그리고 다양한 프로세스는 같은 버전을 유지하는 것을 권장한다.

하지만 모든 프로세스의 버전을 맞출 수 없다면, Kubernetes는 프로세스의 모든 버전에 대해 backward compatibility를 갖추고 있어야 할까?

현실적으로 그것은 불가능하기 때문에 Kubernetes는 버전과 프로세스의 하위 버전 지원의 범위를 정해놓고 있다.

Kubernetes의 버전은 api-server의 버전을 기준으로 한다.

 

프로세스 지원 버전 범위 예시
kube-apiserver X v1.10
controller-manager X-1 v1.9 or v1.10
kube-scheduler X-1 v1.9 or v1.10
kubelet X-2 v1.8 or v1.9 or v1.10
kube-proxy X-2 v1.8 or v1.9 or v1.10
kubectl X+1 > X-1 v1.9 or v1.10 or v1.11

 

또한 kubernetes를 업그레이드 할 때에는 minor 버전을 하나씩 올리는 것을 권장한다. (eg,. v1.9 -> v1.10 -> v1.11)

(버전 명 : v{major}.{minor}.{patch})

 

Kubernetes를 설치할 때와 마찬가지로 업그레이드 시에도 Manual하게 하는 방법과 kubeadm을 사용하는 방법이 있다.

이번에도 역시 kubeadm으로 업그레이드를 진행한다.

 

 

Upgrade Strategy

Kubernetes는 여러 노드로 구성되어 있기 때문에 모든 노드를 전부 업그레이드 해주어야 한다.

이 과정에서 노드의 downtime에 따른 3가지 전략이 있다.

 

1. Master 노드를 업그레이드한 후, 모든 Worker 노드를 한 번에 업그레이드 한다.

당연하겠지만 모든 Worker 노드의 kubelet, kube-proxy 등이 한번에 재시작 되기 때문에 클러스터에서 동작하는 Pod의 downtime이 존재한다.

 

2. Master 노드를 업그레이드한 후, 한 Worker 노드씩 업그레이드 한다.

Worker 노드를 하나씩 재시작하며 업그레이드를 진행한다. 업그레이드 하기 전 node drain을 통해 노드에 있는 Pod를 퇴출시킨 뒤 업그레이드 한다.

 

3. 업그레이드가 이미 된 새로운 Worker 노드를 추가하고 기존 노드 하나를 업그레이드 한다.

언뜻 2번과 비슷한 전략 같지만 이렇게 진행하기 위해선 전체 Worker 노드 수 + 1개의 노드가 필요하다. 업그레이드 중에도 노드의 갯수를 동일하게 유지할 수 있기 때문에 클러스터의 리소스 양을 유지할 수 있다.

 

Kubeadm Upgrade

이제 kubeadm을 사용해 노드 업그레이드를 진행해보자.

$ kubectl get node
NAME     STATUS   ROLES    AGE    VERSION
master   Ready    master   112s   v1.16.0
node01   Ready    <none>   82s    v1.16.0

2개의 노드가 있고 각 노드의 버전은 v1.16.0이다.

노드 하나씩 업그레이드 하는 Strategy 2를 사용해서 업그레이드 해보자.

kubeadm을 사용하면 현재 업그레이드 할 수 있는 버전을 알 수 있다.

$ kubeadm upgrade plan
[upgrade/config] Making sure the configuration is correct:
[upgrade/config] Reading configuration from the cluster...
...
...
Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply':
COMPONENT   CURRENT       AVAILABLE
Kubelet     2 x v1.16.0   v1.17.7

Upgrade to the latest stable version:

COMPONENT            CURRENT   AVAILABLE
API Server           v1.16.0   v1.17.7
Controller Manager   v1.16.0   v1.17.7
Scheduler            v1.16.0   v1.17.7
Kube Proxy           v1.16.0   v1.17.7
CoreDNS              1.6.2     1.6.5
Etcd                 3.3.15    3.4.3-0

You can now apply the upgrade by executing the following command:

        kubeadm upgrade apply v1.17.7

Note: Before you can perform this upgrade, you have to update kubeadm to v1.17.7.

v1.17.7 버전으로 업그레이드 할 수 있다고 나온다.

 

v1.17.7으로 업그레이드 하기 위해서는 kubeadm의 버전이 일치해야한다.

일치하지 않을 경우 kubeadm 버전을 업그레이드 하고

kubeadm upgrade plan v1.17.7을 통해 해당 버전으로 업그레이드가 가능한지 확인한다.

$ apt-get upgrade kubeadm=1.17.7-00

$ kubeadm version
kubeadm version: &version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.7", GitCommit:"b4455102ef392bf7d594ef96b97a4caa79d729d9", GitTreeState:"clean", BuildDate:"2020-06-17T11:37:34Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}

$ kubeadm upgrade plan v1.17.7
[upgrade/config] Making sure the configuration is correct:
[upgrade/config] Reading configuration from the cluster...
[upgrade/config] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'
[preflight] Running pre-flight checks.
[upgrade] Making sure the cluster is healthy:
[upgrade] Fetching available versions to upgrade to
[upgrade/versions] Cluster version: v1.16.0
[upgrade/versions] kubeadm version: v1.17.7

Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply':
COMPONENT   CURRENT       AVAILABLE
Kubelet     2 x v1.16.0   v1.17.7

Upgrade to the latest version in the v1.16 series:

COMPONENT            CURRENT   AVAILABLE
API Server           v1.16.0   v1.17.7
Controller Manager   v1.16.0   v1.17.7
Scheduler            v1.16.0   v1.17.7
Kube Proxy           v1.16.0   v1.17.7
CoreDNS              1.6.2     1.6.5
Etcd                 3.3.15    3.4.3-0

You can now apply the upgrade by executing the following command:

        kubeadm upgrade apply v1.17.7

_____________________________________________________________________

 

 

업그레이드를 하기 전 노드에서 Pod를 퇴출시키는 명령어에 대해 알아보자.

# <NODE>의 Pod 퇴출 및 unscheduling
$ kubectl drain <NODE>

# <NODE> unscheduling
$ kubectl cordon <NODE>

# <NODE> scheduling
$ kubectl uncordon <NODE>

drain은 현재 Pod를 모두 퇴출시키고 앞으로도 kube-scheduler에 의해 Pod가 해당 노드로 스케쥴링 되지 않는다.

$ kubectl describe node master
Name:               master
...
...
Taints:             node.kubernetes.io/unschedulable:NoSchedule

$ kubectl get node
NAME     STATUS                     ROLES    AGE   VERSION
master   Ready,SchedulingDisabled   master   11m   v1.16.0
node01   Ready                      <none>   10m   v1.16.0

위와 같이 스케쥴링이 되지 않도록 Taint가 설정되고 STATUS에 SchedulingDisabled가 나온다.

cordon은 drain처럼 스케쥴링이 되지 않지만 현재 실행중인 Pod는 퇴출시키지 않는다.

uncordon은 drain/cordon된 node를 다시 스케쥴링 할 수 있도록 변경한다.

 

 

master 노드에 있는 Pod를 퇴출시킨 두 업그레이드 한다.

$ kubectl drain master --ignore-daemonsets
node/master already cordoned
WARNING: ignoring DaemonSet-managed Pods: kube-system/kube-proxy-7qcdn, kube-system/weave-net-k2z8q
node/master drained

$ kubeadm upgrade apply v1.17.7
...
...
[upgrade/successful] SUCCESS! Your cluster was upgraded to "v1.17.7". Enjoy!
...

$ kubectl get node
NAME     STATUS                     ROLES    AGE   VERSION
master   Ready,SchedulingDisabled   master   31m   v1.16.0
node01   Ready                      <none>   30m   v1.16.0

$ apt-get install kubelet=1.17.7-00

$ kubectl get node
NAME     STATUS                     ROLES    AGE   VERSION
master   Ready,SchedulingDisabled   master   33m   v1.17.7
node01   Ready                      <none>   32m   v1.16.0

kubeadm으로 업그레이드를 한 뒤 kubelet까지 업그레이드를 해야 노드의 업그레이드가 완료된다.

 

kubeadm과 kubelet은 자동적으로 업그레이드가 되면 안되기 때문에 자동 업그레이드를 막아준다.

$ apt-mark hold kubeadm
kubeadm set on hold.

$ apt-mark hold kubelet
kubelet set on hold.

 

이제 master노드를 다시 스케쥴링 될 수 있도록 만든다.

$ kubectl uncordon master
node/master uncordoned

$ kubectl get node
NAME     STATUS   ROLES    AGE   VERSION
master   Ready    master   34m   v1.17.7
node01   Ready    <none>   33m   v1.16.0

 

이제 node01을 업그레이드 해보자.

$ kubectl drain node01
WARNING: ignoring DaemonSet-managed Pods: kube-system/kube-proxy-hggqf, kube-system/weave-net-rw9th
evicting pod kube-system/coredns-6955765f44-pwtbn
evicting pod kube-system/coredns-6955765f44-tnbjn
pod/coredns-6955765f44-pwtbn evicted
pod/coredns-6955765f44-tnbjn evicted
node/node01 evicted

$ ssh node01

node01 $ kubeadm upgrade node
[upgrade] Reading configuration from the cluster...
[upgrade] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'
[upgrade] Skipping phase. Not a control plane node[kubelet-start] Downloading configuration for the kubelet from the "kubelet-config-1.17" ConfigMap in the kube-system namespace
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[upgrade] The configuration for this node was successfully updated!
[upgrade] Now you should go ahead and upgrade the kubelet package using your package manager.

node01 $ apt-get install kubelet=1.17.7-00

node01 $ apt-mark hold kubeadm
kubeadm set on hold.

node01 $ apt-mark hold kubelet
kubelet set on hold.

node01 $ logout

$ kubectl uncordon node01
node/node01 uncordoned

$ kubectl get node
NAME     STATUS   ROLES    AGE   VERSION
master   Ready    master   42m   v1.17.7
node01   Ready    <none>   42m   v1.17.7

 

이미 master 노드에서 업그레이드한 kubernetes의 정보가 있기 때문에 kubeadm upgrade node 를 통해서 업그레이드 할 수 있다.

 

정리하자면, 업그레이드 순서는 아래와 같다.

 

1. master node drain (kubectl drain <MASTER_NODE>)

2. upgrade master node (apt-get install kubeadm=<VERSION> kubeadm upgrade apply <VERSION>

3. upgrade kubelet (apt-get install kubelet=<VERSION>)

4. master node uncordon (kubectl uncordon <MASTER_NODE>)

 

5. worker node drain (kubectl drain <WORKER_NODE>)

6. upgrade worker node (kubeadm upgrade node)

7. upgrade kubelet (apt-get install kubelet=<VERSION>)

8. worker node uncordon (kubectl uncordon <WORKER_NODE>)

본 포스팅은 Udemy Certificated Kubernetes Administrator 강좌를 공부한 내용입니다.

 

Certified Kubernetes Administrator (CKA) Practice Exam Tests

Prepare for the Certified Kubernetes Administrators Certification with live practice tests right in your browser - CKA

www.udemy.com

 

Kubernetes 자체적으로는 완전하게 monitoring과 logging에 대한 솔루션을 제공해 주지 않는다.

Metric-Server, Prometheus, Elastic Stack 등 별도의 솔루션을 이용해 모니터링 시스템을 구축하는 것을 권장한다.

 

이번에는 Metric-Server를 이용한 간략한 Monitoring과 Logging에 대해 살펴보자.

 

Monitoring

 

각 node의 kubelet에 있는 cAdvisor는 docker와 같은 runtime container로부터 container의 cpu/memory 등 metric을 수집한다.

수집된 metric은 Metric-Server에 모여 kubectl 명령어로 node와 pod의 상태를 모니터링할 수 있다.

 

Metric-Server 설치

$ git clone https://github.com/kodekloudhub/kubernetes-metrics-server.git

$ cd kubernetes-metrics-server/

$ kubectl create -f .
clusterrole.rbac.authorization.k8s.io/system:aggregated-metrics-reader created
clusterrolebinding.rbac.authorization.k8s.io/metrics-server:system:auth-delegator created
rolebinding.rbac.authorization.k8s.io/metrics-server-auth-reader created
apiservice.apiregistration.k8s.io/v1beta1.metrics.k8s.io created
serviceaccount/metrics-server created
deployment.apps/metrics-server created
service/metrics-server created
clusterrole.rbac.authorization.k8s.io/system:metrics-server created
clusterrolebinding.rbac.authorization.k8s.io/system:metrics-server created

Metric-Server Monitoring

$ kubectl top node
NAME     CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
master   152m         7%     1261Mi          66%
node01   2000m        100%   939Mi           24%

$ kubectl top pod
NAME       CPU(cores)   MEMORY(bytes)
elephant   12m          50Mi
lion       869m         1Mi
rabbit     959m         1Mi

 

Metric-Server가 배포되면 위와 같은 명령어를 이용해 node와 pod의 CPU/Memory 정보를 확인할 수 있다.

 

위와 같이 사용할 경우 현재의 Metric을 조회할 수 있지만 과거의 Metric과 함께 조회하거나 Metric 기반의 알람 등 Monitoring을 활용하기 위해서는 Prometheus 등의 솔루션을 사용해야 한다.

Logging

Kubernetes Logging은 각 node에서 실행 중인 container의 로그를 조회할 수 있게 해준다.

 

# <POD_NAME> Pod의 Container 로그 조회
$ kubectl logs <POD_NAME>

# <POD_NAME> Pod의 <CONTAINER_NAME> 로그 조회
$ kubectl logs <POD_NAME> <CONTAINER_NAME>

# <POD_NAME> Pod의 Container 로그 실시간 조회
$ kubectl logs -f <POD_NAME>

docker CLI를 이용해 log를 조회하는 방법과 비슷하다.

Monitoring과 마찬가지로 logging 역시 node에 저장하는 log에 제한이 있다보니 과거의 log까지 저장하기 위해서는 Elastic Search 등의 솔루션을 사용해야 한다.

본 포스팅은 Udemy Certificated Kubernetes Administrator 강좌를 공부한 내용입니다.

 

Certified Kubernetes Administrator (CKA) Practice Exam Tests

Prepare for the Certified Kubernetes Administrators Certification with live practice tests right in your browser - CKA

www.udemy.com

 

DaemonSet

Kubernetes의 node를 모니터링한다고 생각해보자.

각 node에는 docker daemon에서 모이는 로그, node의 CPU/Memory 사용량 등 수집해야할 정보가 많다.

이런 데이터를 수집하는 agent가 모든 node에 배포되어 이를 중앙 저장소에 모은다고 했을 때,

이 agent를 Pod로 생성할 수 있을까?

이를 위해서는 Pod가 모든 node에 하나씩 생성되어 있어야 한다.

이를 위해 있는 것이 DaemonSet이다.

 

Node가 새로 생성되거나 삭제될 때 DaemonSet의 Pod 역시 생성/삭제 된다.

DaemonSet을 생성하는 방법은 Deployment와 유사하다.

 

apiVersion: apps/v1
kind: DaemonSet
metadata:
  creationTimestamp: null
  labels:
    app: elasticsearch
  name: elasticsearch
spec:
  selector:
    matchLabels:
      app: elasticsearch
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: elasticsearch
    spec:
      containers:
      - image: k8s.gcr.io/fluentd-elasticsearch:1.20
        name: fluentd-elasticsearch
        resources: {}
$ kubectl create -f daemonset.yaml
daemonset.apps/elasticsearch created

$ kubectl get pod -o wide
NAME                  READY   STATUS    RESTARTS   AGE   IP          NODE     NOMINATED NODE   READINESS GATES
elasticsearch-l87cf   1/1     Running   0          29s   10.44.0.1   node01   <none>           <none>

$ kubectl get daemonset
NAME            DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
elasticsearch   1         1         1       1            1           <none>          34s

 

 

DaemonSet 역시 Pod이기 때문에 이전에 배운 Taint와 NodeAffinity가 마찬가지로 적용될지 살펴보자.

 

Taint & Toleration

$ kubectl taint nodes node01 color=red:NoExecute
node/node01 tainted

$ kubectl get daemonset
NAME            DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
elasticsearch   0         0         0       0            0           <none>          3m14s

$ kubectl get node
NAME     STATUS   ROLES    AGE     VERSION
master   Ready    master   5m8s    v1.16.0
node01   Ready    <none>   4m25s   v1.16.0

color=red taint를 node01에 적용했더니 DaemonSet의 Pod 갯수가 0이 된걸 확인할 수 있다.

master node 역시 node-role.kubernetes.io/master:NoSchedule taint가 설정되어 있어 Pod가 생성되지 않았다.

 

 

Node Affinity

apiVersion: apps/v1
kind: DaemonSet
metadata:
  creationTimestamp: null
  labels:
    app: elasticsearch
  name: elasticsearch
spec:
  selector:
    matchLabels:
      app: elasticsearch
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: elasticsearch
    spec:
      containers:
      - image: k8s.gcr.io/fluentd-elasticsearch:1.20
        name: fluentd-elasticsearch
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: size
                operator: In
                values:
                - small
$ kubectl label node node01 size=large

$ kubectl create -f daemonset-small.yaml
daemonset.apps/elasticsearch created

$ kubectl get daemonset
NAME            DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
elasticsearch   0         0         0       0            0           <none>          12s

size=large 로 label이 된 node에 NodeAffinity size=small인 daemonset의 pod가 생성되지 않은 것을 확인할 수 있다.

참고로 Node Affinity는 kubernetes 1.12 버전 이후에 적용되었다고 한다.

 

 

 

Static Pod

Kubernetes는 위 그림처럼 Control Plane과 kubelet에 의해 관리되는 Worker Node들이 존재한다.

Control Plane의 역할을 하는 api-server, kube-scheduler 등이 없다면 Worker Node는 동작하지 않을까?

각 노드에서 Container를 실행시키고 관리하는 것은 kubelet이기 때문에 Control Plane이 없더라도 각 Worker Node는 정상 동작한다.

하지만 yaml 형식으로 api-server에 Pod 생성을 스케쥴링할 수 없게 된다.

 

kubelet은 각 node에 특정 path(/etc/kubernetes/manifests)에서 control plane이 아닌 노드 자체적으로 생성하고 관리할 수 있는 Pod yaml을 가지게 할 수 있다.

여기서 생성되는 Pod는 api-server를 통해서가 아닌 kubelet이 자체적으로 path로부터 pod를 생성/재생성/삭제 한다.

이를 Static Pod이라고 한다.

 

Static Pod을 이용하는 곳은 어디일까?

Master Node에서 Control Plane의 역할을 하는 Pod를 Static Pod을 이용해 생성할 수 있다.

 

Static Pod는 kubelet이 api-server에 pod에 대한 정보를 주기 때문에 kubectl을 사용해 조회가 가능하다.


 

 

 



 

 

 

본 포스팅은 Udemy Certificated Kubernetes Administrator 강좌를 공부한 내용입니다.

 

Certified Kubernetes Administrator (CKA) Practice Exam Tests

Prepare for the Certified Kubernetes Administrators Certification with live practice tests right in your browser - CKA

www.udemy.com

CKA 준비 (6) - Scheduling 1 (Manual, taint and toleration)를 통해 Node의 입장에서 POD의 실행 여부를 판단하는 taint & toleration에 대해 배웠다.

Node Selector와 Node Affinity 역시 앞선 내용과 비슷하게 POD를 특정 Node에서 실행시켜 물리적 isolation을 하기 위한 개념이다.

 

이 둘의 의미와 사용 방법에 대해 살펴보고 taint & toleration과 어떻게 같이 사용될 수 있는지 확인해보자.

 

Node Selector

앞서 POD의 spec에서 nodeName 을 통해 POD가 실행될 Node를 manual 적으로 실행시킬 수 있다는 것을 배웠다.

Node Selector는 그와 비슷하지만 하나의 node에서가 아닌 Node Selector에 설정된 Label을 가지고 있는 Node 중에서 하나를 선택한다.

 

Node Label 지정

$ kubectl labels node <NODE_NAME> <label-key>=<label-value>

label을 지정할 <NODE_NAME>에 대해 key-value를 설정한다.

 

POD NodeSelector 지정

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: nginx
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Never
  nodeSelector:
    <label-key>: <label-value>

spec에서 nodeSelector를 통해 실행될 node의 label key-value를 설정한다.

 

 

Node Affinity

Node Selector의 한계점

node의 CPU/Memory 크기에 따라 label을 지정했다고 가정해보자.

$ kubectl labels node node01 size=small
$ kubectl labels node node02 size=medium
$ kubectl labels node node03 size=large

새로운 POD를 실행시킬 때 해당 POD는 size=medium 또는 size=large 둘 중 어느 node에서 실행되도 상관없다면 이것을 nodeSelector로 어떻게 표현할 수 있을까?

같은 의미로 size=small만 아닌 node에서 POD를 실행시키고 싶다면 어떻게 표현할 수 있을까?

이러한 표현을 위해서는 operator가 필요하고 이를 제공해주는 것이 Node Affinity이다.

 

Node Affinity 설정

kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: nginx
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Never
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: size
            operator: In
            values:
            - medium
            - large

In operator를 통해 nginx POD를 size=medium 또는 size=large label이 있는 Node에서 실행시킬 수 있다.

 

Node Affinity가 바라보는 node의 label은 동적으로 생성/삭제가 가능하기 때문에 이에 대한 설정을 할 수 있다.

위의 예제에서 requiredDuringSchedulingIgnoredDuringExecution 가 설정 부분이 된다.

 

  Scheduling Execution
Required POD를 생성할 때, node affinity가 일치하지 않으면 해당 node에 scheduling 하지 않는다. Node Label 변경 등의 이벤트 발생 시, 실행 중인 POD의 node affinity가 일치하지 않으면 퇴출(eviction)한다. 
Ignored POD를 생성할 때, node affinity가 일치하지 않아도 무시한다. 이미 실행 중인 POD는 관여하지 않는다.
Preferred POD를 생성할 때, node affinity가 일치하지 않으면 해당 node에 scheduling 하지 않지만 cluster resource 부족 등으로 POD가 scheduling 되지 않는다면 node에 배정한다. X

 

Taint & Toleration And Node Affinity

앞서 살펴본 Taint & Toleration과 Node Affinity는 비슷한 기능인 것 같다.

둘의 차이가 어떤 것이 있고 같이 사용할 방법에 대해 알아보자.

 

Node1, Node2에 red taint가 존재하고 POD1이 red toleration을 가지고 있다면

POD1은 무조건 Node1과 Node2에 배정될까?

아무런 taint도 존재하지 않는 Node3에도 배정될 수 있다.

 

이 문제를 해결하기 위해 Node Affinity를 쓸 수 있다.

POD1에 대해 large label이 있는 Node에만 배정될 수 있도록 설정했다.

그렇게 되면 POD1은 Node1 또는 Node2에 배정받을 것이다.

하지만 Node Affinity가 없는 새로운 POD2 역시 Node1 또는 Node2에 배정될 수 있다.

이것은 POD1 과 POD2가 서로 물리적으로 isolation이 되지 않는 문제점이 발생한다.

 

 

Tinat & Toleration과 Node Affinity를 같이 사용한다면 위처럼 POD간의 isolation을 완벽하게 할 수 있다.

POD1은 Node Affinity에 의해 Node1 또는 Node2에만 배정받게 되며

POD2는 Taint & Toleration에 의해 Node1 또는 Node2에 배정받지 못한다.

정리

Taint & Toleration과 Node Affinity는 kubernetes app 개발할 때 무심코 지나칠 수 있는 부분인데 시험 준비를 하면서 자세하게 알 수 있었던 것 같다.

 

본 포스팅은 Udemy Certificated Kubernetes Administrator 강좌를 공부한 내용입니다.

 

Certified Kubernetes Administrator (CKA) Practice Exam Tests

Prepare for the Certified Kubernetes Administrators Certification with live practice tests right in your browser - CKA

www.udemy.com

 

이전 포스팅(CKA 준비 (2) - Basic Control Plane Components )을 통해 POD가 배정받을 때 어떤 Worker Node에 배정받을지를 kubernetes Control Plane의 kube-scheduler가 결정한다고 했다.

 

여기서 배우게될 주된 주제는 POD가 어떤 node에 배정되고 퇴출(eviction)될지에 대한 내용이다.

이전 포스팅에서 namespace는 논리적인 단위로 POD 들을 격리(isolation) 시킨다고 했다.

그렇다면 실제로 각 POD의 역할에 맞게 다른 node에 POD를 실행시켜 물리적으로 격리시킬 방법이 있을까?

kubernetes에서는 다양한 방법을 통해 이러한 기능을 제공해준다.

 

Manual

kubernetes에서는 POD spec에서 배정될 node를 직접 명시할 수 있게 제공해준다.

apiVersion: v1
kind: Pod
metadata:
  labels:
    run: nginx
  name: nginx
spec:
  nodeName: master
  containers:
  - image: nginx
    name: nginx
  dnsPolicy: ClusterFirst
  restartPolicy: Never

이렇게 실행할 경우 POD는 kube-scheduler에 의해서 scheduling 되지 않고 바로 master node에 배정된다.

$ $ kubectl get pods -o wide
NANAME    READY   STATUS    RESTARTS   AGE    IP           NODE     NOMINATED NODE   READINESS GATES
ngnginx   1/1     Running   0          102s   10.244.0.4   master   <none>           <none>

하지만  Scheduler에 의하지 않고 명시적으로 node를 명시하는 것은 여러 node의 리소스를 다양한 application이 사용한다는 kubernetes의 컨셉과는 맞지 않는다.

 

Taint & Toleration

Node는 POD가 실제로 실행되는 물리 또는 가상 머신이다.

각 Node는 자의적으로 자신이 실행시켜줄 POD를 구분지을 수 있어야 한다.

 

이러한 기능을 제공해주는게 Taint & Toleration이다.

 

사전적으로 Taint는 더러움, 감염 등을 의미한다.

kubernetes는 각 노드에 taint를 설정해 감염으로부터 용인(toleration)된 POD만 해당 node에서 실행시켜준다.

 

 

Pod 1은 red와 blue taint에 대해 toleration이 있기 때문에 Node1, Node2에 실행될 수 있고

Pod 2는 허용된 toleration이 없기 때문에 3 곳 모두에서 실행될 없고

Pod 3은 green toleration이 있기 때문에 Node 3에서 실행될 수 있다.

 

실제 taint와 toleration 설정 방법을 살펴보자.

 

taint 설정

$ kubectl taint nodes {NODE_NAME} key=value:{TAINT_EFFECT}

taint를 지정하고 싶은 NODE_NAME을 명시하고 key와 value를 입력한다.

그리고 TAINT_EFFECT를 통해 taint에 허용되지 않은 POD에 대한 node의 액션을 지정한다.

TAINT_EFFECT 내용
NoSchedule taint가 허용되지 않는 POD는 Scheduling 하지 않는다.
이미 실행 중인 POD는 관여하지 않는다.
PreferNoSchedule taint가 허용되지 않는 POD는 Scheduling 하지 않는 것을 시도한다.
하지만 cluster의 리소스가 부족한 것과 같은 상황이 되면 toleration이 만족하지 않아도 node에 Scheduliig이 된다.
NoExecute taint가 허용되지 않는 POD는 Scheduling 하지 않는다.
이미 실행 중인 POD도 eviction(퇴출) 한다.

taint 를 삭제할 때는 가장 뒤에 "-"를 붙인다.

$ kubectl taint nodes {NODE_NAME} key=value:{TAINT_EFFECT}-

 

toleration 설정

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: nginx
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Never
  tolerations:
  - key: "color"
    operator: "Equal"
    value: "red"
    effect: "NoSchedule"
status: {}

tolerations은 위와 같이 key/operator/value/effect를 명시한다.

tolerations 설정이 taint와 일치하면 POD는 해당 node에 배정될 수 있다.

(node에 여러개의 taint가 있다면 pod는 모든 taint를 만족해야 배정될 수 있다.)

 

toleration seconds

taint effect인 NoExecute를 활용한 toleration seconds에 대해 알아보자.

 

NoExecute는 이미 실행중인 POD에 대해서도 toleration이 맞지 않으면 퇴출한다.

 

$ kubectl taint nodes node01 color=red:NoExecute

위와 같이 설정되어 있다면 color=red:NoExecute에 만족되지 않는 POD는 node01에서 모두 퇴출된다.

그런데 toleration을 만족하더라도 특정 시간 이후에는 퇴출되도록 할 수 있다.

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: nginx
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Never
  tolerations:
  - key: "color"
    operator: "Equal"
    value: "red"
    effect: "NoExecute"
    tolerationSeconds: 3600
status: {}

 

위처럼 설정되어 있다면 nginx POD는 node01에서 3600초 이후에 퇴출된다.

 

이 기능은 kubernetes의 node controller에 의해서 사용된다.

 

Control Plane의 controller-manager는 모든 node의 상태를 모니터링 한다.

그런데 특정 node가 응답하지 않는다면 controller-manager는 node에 아래와 같은 taint를 지정한다.

key: node.kubernetes.io/unreachable
effect: NoExecute

또한 모든 POD는 아래와 같은 toleration이 default로 설정된다.

tolerations:
- key: "node.kubernetes.io/unreachable"
  operator: "Exists"
  effect: "NoExecute"
  tolerationSeconds: 6000

즉, node의 상태가 이상하다고 판단되면 node controller는 taint를 설정하고 POD는 6000초 후에 해당 node에서 퇴출된다.

퇴출된 POD는 다른 정상상태의 node에 새롭게 scheduling된다.

 

이처럼 tolerationSeconds를 이용해 node 상태에 따른 scheduling이 가능하다.

 

더 자세한 내용은 아래 링크를 참고하자.

 

https://kubernetes.io/ko/docs/concepts/scheduling-eviction/taint-and-toleration/

 

테인트(Taints)와 톨러레이션(Tolerations)

 

kubernetes.io

 

정리

kubernetes의 scheduling에 대해서는 모르는 부분이 많았는데 도움이 많이 되었다.

본 포스팅은 Udemy Certificated Kubernetes Administrator 강좌를 공부한 내용입니다.

 

Certified Kubernetes Administrator (CKA) Practice Exam Tests

Prepare for the Certified Kubernetes Administrators Certification with live practice tests right in your browser - CKA

www.udemy.com

 

Kubernetes는 어플리케이션 사이의 isolation을 위해 namespace라는 리소스 제공하고 있다.

여기서 isolation은 실제 리소스를 물리적으로 격리시키는 것이 아니라 논리적인 단위이다.

또한 POD를 외부에서 접근할 수 있는 endpoint를 제공하기 위해 service라는 리소스를 제공한다.

이 둘에 대해 알아보자.

 

namespace

하나의 kubernetes cluster에서 dev application과 prod application을 같이 운영한다고 가정해보자.

개발자는 dev application을 수시로 업데이트 할 것이고 prod application은 정기 점검이나 새로운 버전을 release할 때 접근할 것이다.

그런데 dev와 prod의 POD가 같은 cli로 보이게 된다면 POD 설정에 실수를 범할 가능성이 높아진다.

이뿐 아니라 kubernetes의 control plane의 역할을 하는 POD 역시 같이 보인다면 위험성이 더 높아진다.

 

namespace는 이러한 문제를 해결할 수 있다.

namespace 역시 kubernetes의 리소스로 관리되기 때문에 kubectl 명령어를 통해 생성/삭제를 할 수 있다.

 

kubernetes의 control plane POD는 kube-system namespace에 존재하고 해당 namespace의 POD를 조회할 때는

-n kube-system option을 사용하면 된다.

$ kubectl get pods -n kube-system
NAME                                      READY   STATUS    RESTARTS   AGE
coredns-66bff467f8-8wk9d                  1/1     Running   0          49m
coredns-66bff467f8-hwqm2                  1/1     Running   0          49m
etcd-master                               1/1     Running   0          49m
katacoda-cloud-provider-58f89f7d9-v79q2   1/1     Running   0          49m
kube-apiserver-master                     1/1     Running   0          49m
kube-controller-manager-master            1/1     Running   0          49m
kube-flannel-ds-amd64-9f4js               1/1     Running   0          48m
kube-flannel-ds-amd64-9v7l9               1/1     Running   0          49m
kube-keepalived-vip-7fn2r                 1/1     Running   0          48m
kube-proxy-ccm7p                          1/1     Running   0          49m
kube-proxy-ttfhw                          1/1     Running   0          48m
kube-scheduler-master                     1/1     Running   0          49m

 

namespace 생성

$ kubectl create namespace dev
namespace/dev created

 

namespace 삭제

$ kubectl delete namespace dev
namespace "dev" deleted

 

namespace 조회

$ kubectl get namespace
NAME              STATUS   AGE
default           Active   54m
kube-node-lease   Active   54m
kube-public       Active   54m
kube-system       Active   54m

 

cluster context

유저가 kubectl을 사용하기 위한 kubernetes의 정보는 ~/.kube/config에 있는데 해당 configuration을 통해 여러 k8s cluster와 namespace를 조회/변경할 수 있다.

 

context 조회

$ kubectl config get-contexts
CURRENT   NAME                          CLUSTER      AUTHINFO           NAMESPACE
*         kubernetes-admin@kubernetes   kubernetes   kubernetes-admin

 

context 추가

$ kubectl config set-context cluster1_kube-system --cluster=cluster1 --namespace=kube-system --user=cluster1
Context "cluster1_kube-system" created.
$ kubectl config get-contexts
CURRENT   NAME                          CLUSTER      AUTHINFO           NAMESPACE
          cluster1_kube-system          cluster1     cluster1           kube-system
*         kubernetes-admin@kubernetes   kubernetes   kubernetes-admin

 

현재 context 변경

$ kubectl config use-context cluster1_kube-system
Switched to context "cluster1_kube-system".
$ kubectl config get-contexts
CURRENT   NAME                          CLUSTER      AUTHINFO           NAMESPACE
*         cluster1_kube-system          cluster1     cluster1           kube-system
          kubernetes-admin@kubernetes   kubernetes   kubernetes-admin

 

앞에서 namespace는 논리적 단위의 isolation이라고 했는데 이 뜻은 dev와 prod namespace에 있는 POD가 실제로 다른 worker node에 존재하지 않고 같은 node에 있을 수 있다는 의미다.

실제 물리적으로 다른 worker node에 배치하고 싶다면 뒤에서 배울 taint and toleration 과 node affinity 등을 사용하면 된다.

 

추가로 namespace에는 resource quota를 지정할 수 있다.

resource quota는 CPU/Memeory/Storage 등 컴퓨팅/스토리지 리소스를 나타내는데 각 namespace에 resource quota를 지정해 namespace 별로 최대 사용할 수 있는 컴퓨팅/스토리지 리소스 량을 제한할 수 있다.

 

service

kubernetes에서 POD를 생성하면 POD는 k8s cluster 내부 IP를 가진다.

이 내부 IP를 이용하면 POD 간의 통신에는 문제가 없다.

하지만 웹페이지와 같이 외부에서 브라우저를 통해 접근해야 하는 POD는 어떻게 제공해줄 수 있을까?

 

그리고 Deployment, ReplicaSet등을 통해 POD의 replicas를 2개 이상 만들었을 때,

이들을 load balacing을 어떻게 제공해줄 수 있을까?

POD를 사용하는 client에서 다수 POD의 IP를 모두 알고 load balancing 로직을 직접 구현해야할까?

 

이러한 문제를 해결할 수 있는 방법을 kubernetes는 service를 통해 제공하고 있다.

위의 예시는 Service의 타입 중 NodePort를 이용한 예시다.

POD는 kubernetes node 중 IP 192.168.1.2를 사용하는 Worker Node에서 실행 중이다.

그리고 Service는 Worker Node의 30008번 포트를 통해 listen을 하고 있고 10.244.0.2 IP를 사용하는 POD에 연결되어 있다.

개발자는 Desktop(192.168.1.5)에서 192.168.1.2:30008를 통해 해당 POD에 접근할 수 있게된다.

 

참고로 NodePort는 Node의 30000~32767 사이의 포트 중 하나를 배정받아 Service를 외부로 노출시킬 수 있게 해준다.

 

Deployment 또는 ReplicaSet을 통해 배포된 POD는 Label과 Selector를 통해 Service가 연결할 수 있고 이를 통해 Loadbalancing을 할 수 있게 된다.

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080

위와 같이 selector를 사용해 POD의 Label을 지정할 수 있다.

 

Service Type

Service의 Type은 아래와 같이 3가지 방식이 있다.

Type 내용
ClusterIP Cluster 내부 IP만 배정받음, 외부로 노출 X
Default Service Type
NodePort Node의 Port를 배정받음, 외부로 노출
LoadBalancing Cloud Provider의 리소스를 제공받음(eg,. AWS ELB)

 

POD에서 Service를 통해 다른 POD에 요청할 때는 Service name을 DNS 네임으로 이용할 수 있다.

동일 namespace의 Service port에 접근 : {SERVICE_NAME}:{PORT} (eg,. nginx:80)

다른 namespace의 Service port에 접근 : {SERVICE_NAME}.{namespace}.svc.cluster.local:{PORT}

(eg,. nginx.dev.svc.cluster.local:80)

 

정리

kubernetes의 service 기능에 대해 정리할 수 있었다

본 포스팅은 Udemy Certificated Kubernetes Administrator 강좌를 공부한 내용입니다.

 

Kubernetes는 Container를 직접적으로 다루지 않는다.

Container를 Pod라는 리소스로 감싸고

Pod를 ReplicationController 또는 ReplicaSet으로 감싸고

ReplicaSet은 최종적으로 Deployment로 깜싸진다.

 

이들의 관계와 역할에 관해 알아보자.

 

Pod

Kubernetes는 Container를 Pod 라는 리소스 단위로 감싼다.

Container와 Pod의 관계는 N:1 이다.

Pod라는 개념을 만든 이유를 살펴보자

Docker와 같은 Runtime Container Engine을 사용해 container를 실행하고 관리하는 경우를 살펴보자

 

1. Backend Container를 실행하고 Frontend로부터 요청을 받는다.

요청 결과는 host와 volumn mount를 통해 log로 쌓는다.

 

 

backend container 실행을 위한 docker 명령어는 아래와 같을 것이다.

$ docker run backend -v /log:/log

2. Backend에서 발생하는 Log를 S3로 전송하는 container를 구동한다.

일반적으로 특정 일 수가 지난 log file은 지워지기 때문에 주기적으로 S3로 file을 업로드하는 container를 실행한다.

logCrawler까지 실행하는 docker 명령어는 아래와 같을 것이다.

$ docker run backend -v /log:/log
$ docker run logCrawler -v /log:/log

3. Backend의 상태를 확인하기 위한 health check container를 구동한다.

 

$ docker run backend -v /log:/log
$ docker run logCrawler -v /log:/log
$ docker run healCheck --link backend:backend

4. Frontend로부터 요청이 많아 Backend Container의 갯수를 늘려 LoadBalancing을 한다.

$ docker run backend1 -v /log:/log1
$ docker run logCrawler1 -v /log:/log1
$ docker run healCheck1 --link backend1:backend

$ docker run backend2 -v /log:/log2
$ docker run logCrawler2 -v /log:/log2
$ docker run healCheck2 --link backend2:backend

Container의 Replication의 갯수가 많아질 수록 연관이 있는 Container의 관리가 복잡해지게 된다.

 

따라서 Kubernetes에서는 위와 같은 현상을 해결하기 위해 Pod라는 리소스 제공해 N개의 Container를 포함하고

같은 Pod로 묶은 Container는 localhost로 네트워크 통신이 가능하며 같은 volume을 공유할 수 있도록 해준다.

나중에 설명될 Kubernetes의 Service를 이용하여 다수의 Pod의 Loadbalancing이 가능하게 된다.

 

Kubernetes의 Resource는 모두 YAML File을 입력으로 받아 생성된다.

따라서 POD 역시 YAML 형식으로 구성된다.

apiVersion: v1
kind: Pod
metadata:
  name: memory-demo
  namespace: mem-example
  labels:
    app: stress
    tier: develop
spec:
  containers:
  - name: memory-demo-ctr
    image: polinux/stress

metadata.labels은 dictionary 구조를 가지고 있어 key/value 쌍을 입력할 수 있다.

spec.containers는 배열 형식으로 다수의 container를 설정할 수 있다.

 

ReplicaSet

Kubernetes에서는 동일한 Container를 가지고 동일한 일을 하는 다수의 POD를 통합해 관리하기 위해 ReplicaSet이라는 개념이 나왔다.

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: frontend
  labels:
    app: guestbook
    tier: frontend
spec:
  replicas: 3
  selector:
    matchLabels:
      tier: frontend
  template:
    metadata:
      labels:
        tier: frontend
    spec:
      containers:
      - name: php-redis
        image: gcr.io/google_samples/gb-frontend:v3

기본 골격은 POD와 같이 ROOT path에는 apiVersion, kind, metadata, spec이 존재한다.

spec.template에는 apiVersion, kind를 제외한 POD의 구조가 입력된다.

spec.replicas는 생성하고 유지할 POD의 갯수를 지정한다.

spec.selector.matchLabels은 POD를 관리하기 위한 요소다.

selector.matchLabels에는 spec.template.metadata.labels에 있는 key/value를 입력해야만 하며 다를 경우 에러가 발생하면서 생성되지 않는다.

(selector.matchLabels를 명시하지 않으면 spec.template.metadata.labels의 app을 기본값으로 사용한다.)

 

Label & Selector

kubernetes cluster에는 다수의 ReplicaSet이 존재할 수 있다.

kubectl의 option --selector <key=value> 을 이용하면 해당 Label을 가진 POD의 목록만을 가져올 수 있다.

$ kubectl get pod --selector tier=frontend
$ kubectl delete pod --selector tier=frontend

또한 나중에 배우게 될 Service에서도 Selector를 이용해 Labels을 가지고 있는 POD을 연결할 수 있다.

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376

위 Service는 metadata.labels 중 app:MyApp 인 field를 가진 POD에 요청을 전달하게 된다.

 

 

Deployment

ReplicaSet으로 배포된 POD의 배포 전략을 구성하고 싶을 때 Deployment 사용할 수 있다.

공식문서에서는 ReplicaSet보단 Deployment를 사용해 POD를 배포할 것을 권장한다.

(Deployment를 생성하면 ReplicaSet도 생성됨)

 

배포 전략이란 새로운 이미지를 배포할 때 Rolling Update/Recreate/Canary 등의 전략을 선택하고

배포가 실패했을 시 Rollback 전략을 말한다.

 

더 자세한 내용은 다음에 해준다고 한다.

기본적인 YAML 형식은 ReplicaSet과 같으며 kind가 Deployment가 변경된다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

 

시험 Tip!

CKA 시험을 볼 때 YAML을 처음부터 작성하는 건 큰 부담이 된다.

따라서 아래와 같은 명령어를 통해 deployment의 기본 골격 YAML을 얻을 수 있다.

$ kubectl create deployment nginx --image=nginx --dry-run -o yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: nginx
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        resources: {}
status: {}

--dry-run option은 deployment를 실제 생성하지 않음을 명시한다.

 

정리

POD에 대한 개념을 다시 정리할 수 있었다.

Label과 Selector의 의미를 정확히 알 수 있었다.

Pod, ReplicaSet, Deployment의 관계를 알 수 있었다.

본 포스팅은 Udemy Certificated Kubernetes Administrator 강좌를 공부한 내용입니다.

Kubernetes Data Plane 이란

Kubernetes를 설치했다면 거기에서 동작시킬 어플리케이션은 어디서 어떻게 동작이 될까?

Data Plane은 사용자의 application이 돌아갈 수 있도록 도와주는 component를 나타낸다.

Data Plane의 component는 모든 Minion(Worker) Node에 위치해 있으며 Control Plane에 응답하거나 Data Plane 간에 요청/응답 한다.

구성요소로는 kubelet, kube-proxy, runtime container등이 있다.

 

 

Runtime Container

실제 Container를 동작/정지시킬 수 있는 daemon을 의미한다.

주로 Docker를 많이 사용하며 이 밖에도 rocket, CRI-O 등이 있다.

역할

  • Container lifecycle 관리

 

Kubelet

각 Worker Node를 배(ship)라고 생각했을 때 kubelet은 이 배의 선장을 의미한다.

 

역할

  • Control Plane의 Scheduler(kube api-server)로부터 배정된 Container를 실행시키거나 종료한다.
  • 주기적으로 Container의 상태를 kube api-server에 report한다.

 

예시 1. Container 생성 요청을 받았을 경우 kubelet은 Container Runtime 데몬에게 명령한다.

예시 2. 주기적으로 Container의 상태를 Control Plane에 report 한다.

 

kubelet은 다른 component와는 다르게 kubeadm을 통해 설치할 수 없고 오직 manual 적으로만 설치가 가능하다고 한다.

역시 설치 부분은 다음에 한다고 한다.

kube-proxy

같은 Kubernetes Cluster에 있는 WAS Pod이 Database Pod에 접근해 SQL을 사용하고 싶다면 어떻게 해야할까?

WAS Pod이 Database Pod의 IP를 알고 있으면 된다.

Worker Node의 Pod은 실행될 때 k8s cluster 내부 IP를 할당받는다.

그런데 Pod은 언제든 다른 node로 옮겨질 수 있고 그 때 다른 IP가 할당될 것이다.

이를 위해 kubernetes에서는 Service를 통해 Pod 간의 네트워크 통신을 한다.

Service는 특정 IP를 배정받아 가지고 있고 또한 연결시켜줄 Pod의 IP를 알고 있다.

그러면 WAS Pod는 Database Pod이 연결되어 있는 Service의 IP를 알면 된다.

Service IP를 매번 알아야 하고 Service IP 역시 변경될 수 있기 때문에 Service Name을 이용해 통신을 할 수 있다.

즉, WAS Pod는 Database Pod이 연결된 Service의 이름을 알면 된다.

 

이러한 통신을 할 수 있게 해주는게 kube-proxy의 역할이다.

kube-proxy는 모든 node에 설치되며 Service와 연결된 Pod IP에 대한 일종의 IPTABLE을 공유하고 있다.

Kubernetes Cluster에서 IP가 변경되는 특정 이벤트가 발생하게 되면 모든 node의 kube-proxy가 자신의 Table을 업데이트 한다.

 

간략하게 아래와 같은 Table 정보를 모든 kube-proxy가 가지고 있게되며 이를 통해 Pod 간의 네트워크 통신을 도와준다.

Service Name Service IP Pod IP
WAS 10.96.0.12 10.32.0.15, 10.32.0.16
Database 10.96.1.12 10.32.1.15, 10.32.1.16

Kubernetes의 네트워크 부분은 이보다 더 많은 내용이 있기 때문에 다음 시간에 더 깊게 살펴본다고 한다.

 

kube-proxy는 kubeadm으로 설치가 가능하며 설치하게 되면 kube-proxy pod를 daemon-set으로 설치한다

정리

Data Plane의 구성요소에 대해 간략하게 살펴보았다.

kubelet과 kube-proxy의 역할에 대해 정리할 수 있었고 Control Plane과의 관계도 자세히 알 수 있었다.

 

+ Recent posts