Blog on Iterating

개발 관련 내용들

개발을 하며 마주치즌 개념에 대해서 하나씩 적어나간다. 간단한 정리로 시작하고 각 내용이 점차 커지거나 정리가 필요하다고 느껴지면 하나의 글로 새롭게 작성하도록 한다.

k8s

컨트롤러

reconcile loop를 수행함으로써 k8s cluster의 current state를 desired state에 맞게 지켜보는 시스템. 특정 리소스를 감시할지 정의한다.

cert-manager

인증서를 발급/갱신해주는 컨트롤러. Certificate, Issuer, ClusterIssuer 같은 커스텀 리소스를 사용한다.

ingress-controller

ingress 리소스에 정의된 라우팅 규칙을 이용해서 실제 네트워크 트래픽을 처리하는 컨트롤러(L7). IngressClass로는 nginx 등을 선택할 수 있다.

리소스

Deployment

ReplicaSet을 이용해 Pod를 어떻게 생성하고 유지할지 정의하는 리소스. 언제 교체되어도 상관없는 stateless pod에 사용된다.

Ingress

HTTP/HTTPS 요청을 어떤 Service로 보낼지 정의하는 리소스. 실제 동작은 없고 Ingress Controller가 Ingress의 라우팅 정보를 참고하여 트래픽을 처리한다.

Namespace

다른 리소스들을 논리적으로 구분하는 공간.

ConfigMap

Pod 실행에 사용되는 앱 설정 값, 마운트, 환경변수 등으로 사용될 수 있다.

Service

트래픽을 어떤 Pod로 전달할지 정의하는 L4 리소스. 종류는 다음의 것들이 있다.

  • ClusterIP - k8s 클러스터 내부에서만 사용하는 통신

  • NodePort - 일반 서버 프로세스처럼 Port 오픈

  • LoadBalancer - 클라우드 로드밸런서 서비스로 통신

selector를 이용해 어떤 pod로 트래픽을 전달할지 정의한다. 이 과정에서 Pod로의 EndPointSlice가 자동으로 생성된다.

Pod가 아닌 외부 IP를 대상으로 트래픽을 보내고 싶다면, selector 없는 Service를 생성한 뒤 해당 Service를 참조하는 EndpointSlice를 직접 생성하면 kube-proxy가 이를 감시하여 트래픽 경로를 구성한다.

EndPointSlice

Service에 연결된 IP 또는 Pod 정보를 가지는 리소스. EndPoints 리소스의 대체 버전이라고 보면 된다. EndPoints로 여러 엔드포인트를 관리하려면 하나의 EndPoints에 여러 엔드포인트를 정의하는데, 이를 여러 조각으로 나눠 확장성있게 관리하기 위해 EndPointSlice가 나왔다. 같은 라벨을 가진 EndPointSlice를 여럿 정의하면 kube-proxy에 의해서 EndPoint Pool이 구성된다.

Secret

KV 구조를 가진 데이터 리소스. 일반적으로 환경변수에 사용된다.

PersistentVolume

pod에 마운트되는 스토리지 볼륨을 정의한다.

PersistentVolumeClaim

정의된 PersistentVolume를 사용하기 위해 정의한다.

StatefulSet

상태를 가지는 Pod를 생성한다. 고유한 식별자를 가지거나 저장소를 가져야할 때 사용한다.

DaemonSet

모든 노드에 하나씩 Pod를 띄우는 리소스

커스텀 리소스

기존 리소스 대신 새로운 리소스를 만드는 것. 이 리소스는 기본 k8s 시스템에서는 이해하지 못한다. 커스텀 리소스를 이용하기 위해서는 이 커스텀 리소스를 바라보는 컨트롤러 정의가 필요하다.

Certificate

cert-manager에서 사용하는 커스텀 리소스. 인증서 발급에 사용되는 정보를 담고 있다.

솔라나

PDA

솔라나 프로그램이 관리하는 주소체계

공개키

program id, 프로그램 정보(seed), "ProgramDerivedAddress"(고정 문자열)에 SHA-256 해시를 적용하여 32바이트 문자열을 생성한다. 직접적으로 사용되지는 않지만 Ed25519 타원곡선 정의를 기준으로 on-curve 여부를 검사한다. 만일 생성된 공개키가 타원곡선 위에 존재한다면 미리 정의된 순서의 숫자인 bump값(255 -> 1)을 추가하여 다시 해시한다. bump 값은 PDA를 재생성하기 위해 해당 프로그램이 별도로 보관한다.

타원곡선을 피하는 이유는, 공개키가 타원곡선에 존재한다면 비밀키가 존재하기 때문이다. PDA는 솔라나 프로그램에 의해서 제어되기 때문에 비밀키가 존재해서는 안된다.

DNS(Domain Name System)

도메인으로 IP를 찾기 위해 만들어진 시스템이다. blog.iterating.io 도메인의 IP를 찾는 과정은 다음과 같다.

  1. Application이 도메인에 대해 요청을 시작한다.

  2. Application은 운영체제 함수인 resolver를 호출한다.

  3. resolver는 도메인 이름을 포함하는 질의를 Local DNS Server에 전달한다.

  4. Local DNS Server

    1. 도메인이 캐시되었다면 IP를 반환하고

    2. 도메인이 캐시되지 않았다면 다음단계로 진행한다.

  5. Local DNS Server는 최상위 도메인을 관리하고 있는 Root DNS Server에 .io DNS Server에 대해 질의한다.

  6. Root DNS Server는 .io DNS Server의 주소를 알려준다.

  7. Local DNS Server는 다시 .io DNS Server에 iterating.io에 대해 질의한다.

  8. .io DNS Server는 iterating.io 권한서버(NS)를 반환한다.

  9. Local DNS Serveriterating.io 권한서버(NS)에 blog.iterating.io를 질의한다.

  10. iterating.io 권한서버(NS)는 blog.iterating.io에 대한 레코드를 반환한다.

  11. Local DNS Server는 blog.iterating.io의 IP를 캐시하고 resolver에 IP를 반환한다.

  12. resolver는 Application에 IP를 반환한다.

컴퓨터 성능 관련 요소

파일 읽기

컴퓨터는 데이터를 읽는 과정에서 파일을 이용한다. 운영체제는 파일을 읽기 위해 커널을 거쳐야 하며, 이 과정은 다음과 같은 높은 비용을 요구한다.

컨텍스트 스위칭

CPU는 사용자 영역과 커널 영역에서 동작한다. 프로그램은 사용자 영역에서 실행되고, 파일 읽기와 같은 시스템 콜은 커널 영역에서 실행된다.

프로그램이 파일 읽기 같은 시스템 콜을 요청하면 CPU 실행 모드는 사용자 영역에서 커널 영역으로 전환된다. 이 과정에서 사용자 영역에서 사용 중이던 실행 상태들이 저장된다. 파일 읽기가 끝나면 CPU 실행 모드는 다시 사용자 영역으로 전환되고, 저장해 두었던 실행 상태를 복원한 뒤 실행을 이어나간다.

이 동작은 CPU가 수행하지만, 저장 및 복원되는 실행 상태는 스레드 단위로 관리된다. 각 스레드의 실행 흐름에서 수행되는 동작인 것이다.

디스크 I/O지연

파일을 읽는 디스크 I/O는 CPU 연산과 무관하기 때문에, 파일 읽기 요청을 수행한 스레드는 대기 상태에 들어간다. 저장장치는 CPU에 비해 접근 속도가 느리기 때문에 많은 요청을 받는 경우 병목현상이 발생하여 응답이 지연된다. 이렇게 되면 디스크 I/O 요청을 했던 스레드의 대기시간이 길어져 성능이 저하된다.

운영체제

스레드

스레드는 CPU가 작업하는 실행 단위이다. 운영체제는 프로세스가 아닌 스레드를 대상으로 스케줄링을 수행하고, 프로세스는 최소 하나 이상의 스레드를 가진다.

스레드는 CPU가 한 번의 실행으로 작업을 끝마치는 대상이 아니라, 스케줄링에 의해서 언제든지 중단되었다가 다시 재개할 수 있는 흐름으로 이해할 수 있다. 스케줄링에 의해 실행 중인 스레드가 변경되면, 컨텍스트 스위칭이 발생하며 이에 따른 오버헤드가 존재한다.

멀티 스레드를 하는 이유는 스레드 블로킹으로 인한 CPU 대기 시간을 줄이기 위함이다. 단일 스레드로 이루어진 프로세스에서 단일 스레드가 블로킹되면 이 스레드는 CPU의 실행 대상에서 제외되며, 프로세스는 동작이 멈추게 된다. 프로세스가 멀티 스레드로 동작한다면 블로킹된 스레드 외의 스레드가 CPU의 실행 대상이 되어 프로세스는 계속 동작할 것이다.

Nodejs

스레드

메인 스레드

javascript 작업. 이벤트 루프는 여기에서 동작한다.

워커 스레드

javascript 외 작업

네트워크

NAT(Network Address Translation)

패킷이 지나갈 때 IP 주소를 변환하는 역할. 패킷의 출발지/목적지 주소를 변경한다.

서브넷 마스크(Subnet Mask)

네트워크라는 것은 IP 체계를 기반으로 하여 장치들 간 구성된 논리적 구조이다. IP를 관리, 분배하는 장치가 있고 다른 장치들은 IP를 할당 받아서 연결을 이루게 된다. 그리고 IP에는 네트워크를 식별하기 위해 192.168.0.1/24 형태를 가지게 되는데, 이 숫자 24로 네트워크의 크기를 계산할 수 있다. 이 주소 형태를 CIDR 표기법이라고 한다.

일반적으로 아이피는 0~255의 숫자(8비트)로 4개로 구성된다. 총 32비트가 되는데, IP뒤의 숫자는 앞에서부터 몇 개의 비트를 네트워크 영역으로 정의하는지를 의미한다. 192.168.0.1/24의 경우 192.168.0.*이 네트워크 영역이 되고 이 네트워크는 192.168.0.1 ~ 192.168.0.254까지 구성될 수 있다. 다음의 두 주소는 논리적으로 예약되어 있어 사용하지 않는다.

  • 192.168.0.0 - 네트워크 주소

  • 192.168.0.255 - 브로드캐스트 주소

이 네트워크의 서브넷 마스크라는 것은 앞 24개의 비트가 1로 구성된 11111111.11111111.11111111.00000000 비트를 말한다. 어떤 장치가 A라는 IP와 통신하려고 할 때 자신의 IP와 서브넷 마스크를 AND 연산, 그리고 A의 IP와 서브넷 마스크를 AND 연산하여 나온 두 결과가 동일하다면 네트워크 내부, 그렇지 않다면 네트워크 외부임을 암시한다.

Ethernet

링크 계층에서 통신하는 방법. 이더넷 프레임을 규격으로 사용한다.

LAN(local area network)

한정된 지역에서의 네트워크. 일반적으로 내부 네트워크를 지칭한다.

LAN에서의 데이터 전송은 대부분 이더넷 기반으로 이루어지며, 이더넷 프레임을 사용해 통신한다. 프레임의 목적지 MAC 주소에 따라 전달 방식이 결정된다.

  • 유니캐스트 - 특정 MAC 주소를 가진 대상과 1:1 통신

  • 브로드캐스트 - 같은 네트워크의 모든 기기에 프레임을 전달.

WAN(wide area network)

멀리 떨어진 지역간 네트워크. 통신사 회선 또는 인터넷 백본을 통해 구성된다. 데이터 전송은 이더넷 프레임을 이용하는 유니캐스트 중심으로 동작한다. 통신은 IP 유니캐스트 중심으로 동작한다.

NIC(Network Information Center)

공인 IP 주소 관리 기관

NIC(Network Interface Card)

데이터 링크 계층과 물리 계층에 존재하며 각 계층에서의 역할을 수행하는 가상/물리 장치. 전송할 데이터를 이더넷 프레임으로 변환해 물리 계층으로 전송한다. 외부로부터 수신한 프레임은 상위 계층인 네트워크 계층에 전달한다. 네트워크를 사용하기 위한 필수적인 장치이다. 다른 말로는 랜카드다.

  • 물리 계층에서 하는 일

    • 전기신호, 광신호 송수신

    • 비트 인코딩/디코딩

    • 링크 속도 및 듀플렉스 협상

  • 데이터 링크 계층에서 하는 일

    • 이더넷 프레임 송수신

    • MAC 주소 필터링

    • FCS(Frame Check Sequence) 검사

MAC(Media Access Control)

MAC 주소는 일반적으로 NIC에 부여된 식별값이다. 소프트웨어적으로 변경될 수 있어 영구적인 값으로 보기는 어렵다.

같은 네트워크(LAN)에서의 데이터 전송은 이더넷 기반의 통신으로 이루어진다. 이더넷 기반 통신은 홉 단위로 전송이 진행되며, MAC 주소는 이번 홉에서 데이터를 전달할 목적지를 의미한다. 이를 위해 이더넷 프레임이 사용되고 이더넷 프레임 헤더는 목적지/출발지 MAC 주소를 포함한다.

오늘날 LAN 환경에서 이더넷 프레임을 중계하는 기기는 스위치이며, 스위치는 수신한 프레임을 목적지 MAC 주소를 기준으로 포워딩한다.

MAC 주소 식별 과정

MAC 주소는 운영체제가 식별, 관리하고 통신 수단으로는 NIC가 이용된다.

  1. 목적지 IP가 내부/외부 네트워크인지 확인. 송신 기기가 IP와 서브넷 마스크를 이용해 네트워크를 식별한다.
    내부 네트워크라면, 데이터를 전달하고자 하는 대상은 같은 네트워크의 기기가 된다.
    외부 네트워크라면, 데이터를 전달하고자 하는 대상은 같은 네트워크의 게이트웨이가 된다.
    어느 쪽이던 대상의 MAC 주소를 식별해야한다.

  1. ARP 캐시 확인. IP에 맵핑된 MAC 주소를 찾는다. 못 찾았다면 3번을 수행한다.

  2. ARP 브로드캐스트 요청. 목적지 MAC 주소를 FF:FF:FF:FF:FF:FF로 설정하고 브로드 캐스트 프레임을 전달한다. 프레임에는 MAC 주소를 확인하고자 하는 IP가 포함된다.

  3. 브로드캐스트 수신. 네트워크 내 모든 기기는 전달받은 요청에 담긴 IP와 자신의 IP를 비교하고 일치한다면 5번을 수행한다.

  4. ARP 유니캐스트 응답. 수신 기기는 자신의 MAC 주소를 응답에 포함시켜 송신 기기에 응답한다.

ARP 프로토콜 규격은 이처럼 브로드캐스트 요청과 유니캐스트 응답 구조로 정의되어 있다.

CSMA/CD(Carrier Sense Multiple Access/Collision Detection)

과거 이더넷에서 사용되던 통신방식. 전송 전 네트워크가 사용중인지 확인하고 전송 중 충돌이 발생하면(충돌 감지) 전송을 중단한 뒤 잠시 기다렸다가 다시 전송한다.

허브

허브는 로컬 네트워크를 구성하기 위한 물리 계층 장치다.

허브는 수신 받는 신호를 모든 포트에 그대로 전달한다. 허브에 연결된 장치들은 하나의 물리적 전송 매체를 공유하며 하나의 콜리전 도메인을 형성한다.

이런 공유 매체 환경에서 충돌 문제를 해결하기 위해 CSMA/CD 방식이 사용된다. CSMA/CD는 충돌을 제거하는 방식이 아니라 충돌을 감지하고 재전송하는 전략이기 때문에 충돌은 여전히 존재한다. 이런 구조적 한계로 요즘에는 스위치로 대체되어 사용되지 않는다.

  • 콜리전(collision) - 둘 이상의 장치가 전송하여 신호가 충돌하는 상황

  • 콜리전 도메인 - 여러 장치가 하나의 공유된 전송 매체를 공유하여 전송 시 충돌이 발생할 수 있는 네트워크 영역

스위치

스위치는 데이터 링크 계층 장치다.

스위치는 각 장치를 포트와 1:1로 연결시켜 목적지 MAC 주소에 이더넷 프레임을 전달한다. 각 포트마다 독립적인 통신을 지원하여 충돌 없이 동시 통신을 가능하게 해준다.

이런점 때문에 LAN 연결 역할을 하던 허브를 기능적으로 대체하게 되었다.

허브와 스위치 비교

  • 허브 - 모든 기기는 하나의 통신 매체를 공유한다. 모든 기기가 신호를 공유 받는다.

  • 스위치 - 모든 기기는 각각의 포트와 1:1로 연결되어 있어 개별적인 신호를 전달 받는다.

브릿지

스위치의 초기 형태로 데이터 링크 계층 장치다. 콜리전 도메인을 여러 개로 분리한다. MAC 주소를 기준으로 프레임을 전달한다.

  • 세그먼트 - 브릿지의 각 포트에 1:1로 연결된 논리적 콜리전 도메인이다. 각 세그먼트 내부는 하나의 공유된 물리적 전송 매체로 이루어져 있다. 브릿지는 여러 세그먼트를 관리하고, 각 세그먼트마다 서로 간섭 없이 독립적으로 동시에 통신할 수 있다. 통신 과정은 다음과 같다.

    • A 기기를 브릿지에 연결 후 통신을 하려고 하면, 브릿지는 출발지 MAC 주소(A 기기)와 프레임이 전송된 세그먼트를 확인하고 MAC 어드레스 테이블에 저장한다.

    • 출발지와 목적지가 같은 세그먼트에 있는 경우, 세그먼트는 콜리전 도메인이기 때문에 프레임 전송과 동시에 세그먼트 내부로 전파되어 목적지로 전달된다. 이 때, 프레임은 브릿지로도 전달되지만 아래 동작을 수행한 뒤 폐기된다.

    • 브릿지는 목적지가 어떤 세그먼트에 있는지 모르는 경우, 출발지 세그먼트를 제외한 모든 세그먼트로 flooding을 수행한다. flooding은 브릿지가 목적지를 알 수 없기 때문에 수행하는 브로드 캐스트다.

    • 브릿지는 목적지가 어떤 세그먼트에 있는지 알고 있는 경우, flooding을 수행할 필요없이 목적지가 속한 세그먼트로 forwarding만 수행하면 된다.

    • 브릿지는 출발지와 목적지가 같은 세그먼트에 있음을 확인하면, 프레임이 다른 세그먼트로 가는 것을 막는 filtering을 수행한다. 이 기능을 이용해 콜리전 도메인을 세그먼트 단위로 나누게 된다.

  • 스위치와 브릿지의 처리방식 - 네트워크의 흐름 이해 목적

    • store-and-forward - 프레임의 모든 부분을 확인 후 전송처리, 에러 감지 가능. 브릿지

    • cut-through - 프레임의 헤드 부분(MAC 주소)을 확인 후 바로 전송처리, 에러 감지 불가능. 스위치

    • fragment-free - 위 두 가지를 섞은 것, 헤드 부분(MAC 주소)보다는 좀 더 많은 데이터를 확인 후 전송, 일부 에러 감지도 가능. 스위치

Looping(루핑)

두 개의 브릿지가 두 개의 링크로 되어 있는 경우 flooding이 무한정 발생하게 되어 네트워크에 과부하가 발생하는 현상. Spanning Tree Protocol을 이용해 중복 경로를 제거해서 해결한다.

라우터

라우터는 네트워크 계층 장치다. IP 주소를 기준으로 패킷을 전달한다. 서로 다른 네트워크인 서브넷을 연결한다. 브로드 캐스트 프레임을 다른 네트워크에 전달하지 않음으로 브로드 캐스트 도메인을 구성한다.

HTTP

HTTP를 설명하기 전에 필요한 용어들을 먼저 정리한다.

용어

TCP

간단한 설명을 적은 링크 - https://blog.iterating.io/draft/read?id=22#9ebc623f-e9ca-4ac9-aef4-08a1f4a6092a

  1. 슬로우 스타트. 네트워크 혼잡을 피하기 위해서 초기 데이터 전송량을 점진적으로 증가시키는 알고리즘이다.

  2. 세그먼트 단위로 순서를 보장하며 전달.

  3. 전송했던 세그먼트가 실패하면 다시 전달.

  4. 3 hand-shake라는 연결 보장 프로토콜 이용.

    1. 요청 : 클라이언트 -> 서버

    2. 응답 + 확인 : 클라이언트 <- 서버

    3. 확인 : 클라이언트 -> 서버

  5. TCP fast open이라고 데이터 전송을 통신 수립 요청에 싣는 방법으로, 3 hand-shake보다 한 번의 통신이 적은 방법도 있다.

    1. 요청, 데이터 동시 전송 : 클라이언트 -> 서버

    2. 응답 + 확인, 데이터 전송 : 클라이언트 <- 서버

HOL Blocking

Head-of-Line Blocking. 앞선 전송이 막히면 뒤의 전송이 대기하는 현상

TLS

데이터 암호화 프로토콜.

  1. 클라이언트의 'client hello' 메세지를 통해 프로토콜 버전, 암호화 알고리즘을 전달하고, 서버는 응답으로 'server hello' 메세지를 통해 서버의 인증서, 암호화 알고리즘을 전달한다.

  2. 위의 통신으로 만들어진 세션키를 이용해 암호화된 메세지를 주고 받는 확인 작업을 거친다.

TLS 1.2
  1. 알고리즘 제안 : 클라이언트 -> 서버

  2. 인증서, 키 생성 정보 전달 : 클라이언트 <- 서버

  3. 키 교환 : 클라이언트 -> 서버

TLS 1.3
  1. 키 공유 : 클라이언트 -> 서버

  2. 키 공유 : 클라이언트 <- 서버

이러한 방법이 가능한 이유는 Diffie-Hellman 키 교환때문이다. (실제로는 TLS 1.2, TLS1.3 모두 Elliptic Curve Diffie-Hellman Ephemeral 사용, Ephemeral는 세션 마다 새로운 키 쌍을 생성한다는 의미이다.)

원리는 타원곡선 암호체계에 간단히 정리해두었다.

RTT

Round Trip Time. 패킷을 보내고 난 뒤 응답 패킷이 도착하는 시간. TCP는 1RTT이다. TLS는 2RTT다. TCP + TLS는 3RTT가 된다. 다만 TLS 1.3의 경우에 위에 설명한 것처럼 단순해지기 때문에 2RTT가 된다.

HTTP/1.0

모든 요청에 대해서 새로운 TCP연결 생성. 계속해서 TCP 연결과 각각의 슬로우 스타르로 인해서 성능이 저하된다.

HTTP/1.1

Keep-Alive를 이용해 TCP연결을 재사용한다. TCP 슬로우 스타트를 회피한다.

다만, TCP 연결을 재사용하더라도 요청, 응답이 순서대로 처리되기 때문에 요청간 병목이 생긴다. 그렇다고 병렬로 요청을 하게 되면 개수의 차이일 뿐이지 HTTP/1.0의 문제가 다시 발생한다. 애플리케이션 계층 레벨의 HOL Blocking.

HTTP/2.0

스트림 도입. 하나의 통로를 이용해서 모든 요청을 바이트 스트림으로 전송하는 방식인데, 세그먼트의 일부 손실이 발생하게 되면 손실 세그먼트 복구까지 모든 스트림이 대기하게 된다. 전송 계층 레벨의 HOL Blocking.

HTTP/3

HTTP/2.0에서 발생한 전송 계층 레벨의 HOL Blocking을 해결하기 위해서 TCP 대신에 UDP를 사용한다. UDP는 순서 보장과 재전송을 제공하지 않기 때문에 QUIC 프로토콜을 이용해 애플리케이션 레벨에서 구현한다.

각 스트림이 독립적으로 관리되어 패킷(UDP 전송 단위) 손실이 발생하더라도 HOL Blocking로 인한 성능 저하가 발생하지 않는다.

TLS 1.3을 내부에 포함하고 있어 TCP 연결과 TLS 연결을 분리해서 수행하지 않기 때문에 1RTT 연결 수립 및 통신이 가능하다.