지난번에 Triton Inference Server에서 HuggingFace 모델 서빙하기를 포스팅 했었는데,
당시에는 HuggingFace에 한국어로 파인튜닝된 LLaMa3 모델을 서빙했었다.

이번에는 벤치마크 LogicKor, Horangi 리더보드에서 높은 순위를 기록하고 있는 ko-gemma-2-9b-it를 서빙하기 위한 과정을 기록해보았다.

TensorRT-LLM Backend의 기본 예제만 따르면 쉽게 서빙할 수 있는 LLaMa3와는 달리, Gemma2는 비교적 최신 모델이라 TensorRT-LLM 버전, TensorRT-LLM Backend 버전, Triton Inferenece Server Container의 버전을 모두 신경써줘야 했다.
TensorRT-LLM v0.13.0부터 Gemma2를 지원한다는 정보를 토대로, 이 포스트에선 다음을 사용한다.

  • Triton Inference Server Container 24.09 (TensorRT-LLM v0.13.0의 dependent TensorRT version인 10.4.0이 설치되어있음)
  • TensorRT-LLM Backend v0.13.0

이후 버전을 사용해도 무방할 것으로 보인다.
(TensorRT-LLM Backend v0.13.0과, TensorRT-LLM v0.15.0이 설치된 Triton Inference Server Container 24.11을 사용해서 테스트 했을 때 동일하게 동작함)

서빙 환경

  • OS: Ubuntu 20.04
  • GPU: NVIDIA RTX4090 * 2
  • GPU Driver Version: 550.127.05

Update the TensorRT-LLM submodule

git clone -b v0.13.0 https://github.com/triton-inference-server/tensorrtllm_backend.git
cd tensorrtllm_backend
git submodule update --init --recursive
git lfs install
git lfs pull

Launch Triton TensorRT-LLM container

docker run -it --net host --shm-size=2g \
    --ulimit memlock=-1 --ulimit stack=67108864 --gpus all \
    -v ~/tensorrtllm_backend:/tensorrtllm_backend \
    -v ~/engines:/engines \
    nvcr.io/nvidia/tritonserver:24.09-trtllm-python-py3

Prepare TensorRT-LLM engines

Download weights from HuggingFace Transformers

cd /tensorrtllm_backend/tensorrt_llm
pip install huggingface-hub
huggingface-cli login
huggingface-cli download "rtzr/ko-gemma-2-9b-it" --local-dir "ko-gemma-2-9b-it"

Convert weights from HF Tranformers to TensorRT-LLM checkpoint

‘world_size’ is the number of GPUs used to build the TensorRT-LLM engine.

CKPT_PATH=ko-gemma-2-9b-it/
UNIFIED_CKPT_PATH=/tmp/checkpoints/tmp_ko-gemma-2-9b-it_tensorrt_llm/bf16/tp2/
ENGINE_PATH=/engines/gemma2/9b/bf16/2-gpu/
VOCAB_FILE_PATH=ko-gemma-2-9b-it/tokenizer.model

python3 ./examples/gemma/convert_checkpoint.py \
    --ckpt-type hf \
    --model-dir ${CKPT_PATH} \
    --dtype bfloat16 \
    --world-size 2 \
    --output-model-dir ${UNIFIED_CKPT_PATH}

Build TensorRT engines

trtllm-build --checkpoint_dir ${UNIFIED_CKPT_PATH} \
             --gemm_plugin auto \
             --max_batch_size 8 \
             --max_input_len 3000 \
             --max_seq_len 3100 \
             --output_dir ${ENGINE_PATH}

Prepare the Model Repository

rm -rf /triton_model_repo
mkdir /triton_model_repo
cp -r /tensorrtllm_backend/all_models/inflight_batcher_llm/* /triton_model_repo/

Modify the Model Configuration

ENGINE_DIR=/engines/gemma2/9b/bf16/2-gpu/
TOKENIZER_DIR=/tensorrtllm_backend/tensorrt_llm/ko-gemma-2-9b-it/
MODEL_FOLDER=/triton_model_repo
TRITON_MAX_BATCH_SIZE=4
INSTANCE_COUNT=1
MAX_QUEUE_DELAY_MS=0
MAX_QUEUE_SIZE=0
FILL_TEMPLATE_SCRIPT=/tensorrtllm_backend/tools/fill_template.py
DECOUPLED_MODE=false

python3 ${FILL_TEMPLATE_SCRIPT} -i ${MODEL_FOLDER}/ensemble/config.pbtxt triton_max_batch_size:${TRITON_MAX_BATCH_SIZE}
python3 ${FILL_TEMPLATE_SCRIPT} -i ${MODEL_FOLDER}/preprocessing/config.pbtxt tokenizer_dir:${TOKENIZER_DIR},triton_max_batch_size:${TRITON_MAX_BATCH_SIZE},preprocessing_instance_count:${INSTANCE_COUNT}
python3 ${FILL_TEMPLATE_SCRIPT} -i ${MODEL_FOLDER}/tensorrt_llm/config.pbtxt triton_backend:tensorrtllm,triton_max_batch_size:${TRITON_MAX_BATCH_SIZE},decoupled_mode:${DECOUPLED_MODE},engine_dir:${ENGINE_DIR},max_queue_delay_microseconds:${MAX_QUEUE_DELAY_MS},batching_strategy:inflight_fused_batching,max_queue_size:${MAX_QUEUE_SIZE}
python3 ${FILL_TEMPLATE_SCRIPT} -i ${MODEL_FOLDER}/postprocessing/config.pbtxt tokenizer_dir:${TOKENIZER_DIR},triton_max_batch_size:${TRITON_MAX_BATCH_SIZE},postprocessing_instance_count:${INSTANCE_COUNT},max_queue_size:${MAX_QUEUE_SIZE}
python3 ${FILL_TEMPLATE_SCRIPT} -i ${MODEL_FOLDER}/tensorrt_llm_bls/config.pbtxt triton_max_batch_size:${TRITON_MAX_BATCH_SIZE},decoupled_mode:${DECOUPLED_MODE},bls_instance_count:${INSTANCE_COUNT}${TRITON_MAX_BATCH_SIZE},decoupled_mode:${DECOUPLED_MODE},bls_instance_count:${INSTANCE_COUNT}

Serving with Triton

‘world_size’ is the number of GPUs you want to use for serving. This should be aligned with the number of GPUs used to build the TensorRT-LLM engine.

python3 /tensorrtllm_backend/scripts/launch_triton_server.py --world_size=2 --model_repo=/triton_model_repo

To Stop Triton Server insider the container

pkill tritonserver

Send an Inference Request

curl -X POST http://localhost:8000/v2/models/ensemble/generate -d '{"text_input": "안녕?", "max_tokens": 100, "bad_words": "", "stop_words": ""}'

Output Example

{"batch_index":0,"context_logits":0.0,"cum_log_probs":0.0,"generation_logits":0.0,"model_name":"ensemble","model_version":"1","output_log_probs":0.0,"sequence_end":false,"sequence_id":0,"sequence_start":false,"text_output":"안녕? \n\n저는 한국어를 배우는 중인 AI입니다. \n\n오늘은 한국어로 대화를 나누고 싶어요. \n\n어떤 주제로 이야기해볼까요? \n\n😊\n"}

참고 링크

GitHub triton-inference-server/tensorrtllm_backend 저장소의 튜토리얼과 NVIDIA/TensorRT-LLM 저장소의 LLaMa 예제를 참고하여
HuggingFace의 MLP-KTLim/llama-3-Korean-Bllossom-8B 모델의 엔진을 생성하고 서빙하는 과정을 정리한 글입니다.

Update the TensorRT-LLM submodule

git clone -b v0.11.0 <https://github.com/triton-inference-server/tensorrtllm_backend.git>
cd tensorrtllm_backend
git submodule update –init –recursive
git lfs install
git lfs pull
cd ..

Launch Triton TensorRT-LLM container

docker run -it --net host --shm-size=2g \\
	--ulimit memlock=-1 --ulimit stack=67108864 --gpus all \\
	-v $(pwd)/tensorrtllm_backend:/tensorrtllm_backend \\
	-v $(pwd)/engines:/engines \\
	nvcr.io/nvidia/tritonserver:24.07-trtllm-python-py3

Prepare TensorRT-LLM engines

cd /tensorrllm_backend/tensorrt_llm

Download weights from HuggingFace Transformers

pip install huggingface-hub
huggingface-cli login
huggingface-cli download "MLP-KTLim/llama-3-Korean-Bllossom-8B" --local-dir "llama-3-Korean-Bllossom-8B"

Build LLaMA v3 8B TP=1 using HF checkpoints directly.

cd /tensorrtllm_backend/tensorrt_llm/examples/llama

Convert weights from HF Tranformers to TensorRT-LLM checkpoint

python3 convert_checkpoint.py --model_dir /tensorrtllm_backend/tensorrt_llm/llama-3-Korean-Bllossom-8B \\
	--output_dir ./tllm_checkpoint_1gpu_tp1 \\
	--dtype float16 \\
  --tp_size 1

Build TensorRT engines

trtllm-build --checkpoint_dir ./tllm_checkpoint_1gpu_tp1 \\
	--output_dir ./tmp/llama/8B/trt_engines/fp16/1-gpu/ \\
	--gemm_plugin auto

Prepare the Model Repository

rm -rf /triton_model_repo
mkdir /triton_model_repo
cp -r /tensorrtllm_backend/all_models/inflight_batcher_llm/* /triton_model_repo/

Modify the Model Configuration

ENGINE_DIR=/tensorrtllm_backend/tensorrt_llm/examples/llama/tmp/llama/8B/trt_engines/fp16/1-gpu
TOKENIZER_DIR=/tensorrtllm_backend/tensorrt_llm/llama-3-Korean-Bllossom-8B
MODEL_FOLDER=/triton_model_repo
TRITON_MAX_BATCH_SIZE=4
INSTANCE_COUNT=1
MAX_QUEUE_DELAY_MS=0
MAX_QUEUE_SIZE=0
FILL_TEMPLATE_SCRIPT=/tensorrtllm_backend/tools/fill_template.py
DECOUPLED_MODE=false

python3 ${FILL_TEMPLATE_SCRIPT} -i ${MODEL_FOLDER}/ensemble/config.pbtxt triton_max_batch_size:${TRITON_MAX_BATCH_SIZE}
python3 ${FILL_TEMPLATE_SCRIPT} -i ${MODEL_FOLDER}/preprocessing/config.pbtxt tokenizer_dir:${TOKENIZER_DIR},triton_max_batch_size:${TRITON_MAX_BATCH_SIZE},preprocessing_instance_count:${INSTANCE_COUNT}
python3 ${FILL_TEMPLATE_SCRIPT} -i ${MODEL_FOLDER}/tensorrt_llm/config.pbtxt triton_backend:tensorrtllm,triton_max_batch_size:${TRITON_MAX_BATCH_SIZE},decoupled_mode:${DECOUPLED_MODE},engine_dir:${ENGINE_DIR},max_queue_delay_microseconds:${MAX_QUEUE_DELAY_MS},batching_strategy:inflight_fused_batching,max_queue_size:${MAX_QUEUE_SIZE}
python3 ${FILL_TEMPLATE_SCRIPT} -i ${MODEL_FOLDER}/postprocessing/config.pbtxt tokenizer_dir:${TOKENIZER_DIR},triton_max_batch_size:${TRITON_MAX_BATCH_SIZE},postprocessing_instance_count:${INSTANCE_COUNT},max_queue_size:${MAX_QUEUE_SIZE}
python3 ${FILL_TEMPLATE_SCRIPT} -i ${MODEL_FOLDER}/tensorrt_llm_bls/config.pbtxt triton_max_batch_size:${TRITON_MAX_BATCH_SIZE},decoupled_mode:${DECOUPLED_MODE},bls_instance_count:${INSTANCE_COUNT}

Serving with Triton

‘world_size’ is the number of GPUs you want to use for serving. This should be aligned with the number of GPUs used to build the TensorRT-LLM engine.

python3 /tensorrtllm_backend/scripts/launch_triton_server.py --world_size=1 --model_repo=/triton_model_repo

To stop Triton Server inside the container

**pkill tritonserver**

Send an Inference Request

curl -X POST <http://localhost:8000/v2/models/ensemble/generate> -d '{"text_input": "한강 작가를 알고 있니?", "max_tokens": 100, "bad_words": "", "stop_words": ""}'

Output Example

{"context_logits":0.0,"cum_log_probs":0.0,"generation_logits":0.0,"model_name":"ensemble","model_version":"1","output_log_probs":[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0],"sequence_end":false,"sequence_id":0,"sequence_start":false,"text_output":"한강 작가를 알고 있니? 한강 작가는 한국의 대표적인 현대 소설가 중 한 명으로, '채식주의', '적도의 남자', '황무지' 등 많은 작품을 발표한 작가다. 그녀의 작품은 주로 인간의 본성, 사회적 규범, 그리고 개인의 자유와 억압을 탐구하는 내용을 담고 있다. 특히 '채식주의'는 한강 작가의 대표작으로, 주인공이 채식을 시작하면서 "}

참고 링크

Overview

  • ML모델 서빙을 위한 오픈소스 플랫폼으로, AI모델의 대규모 배포를 간단하게 하기 위해 설계됨
  • 훈련된 AI모델이 효율적으로 추론할 수 있도록 함
  • 다양한 H/W(NVIDIA GPU, CPU 등)와 프레임워크(TensorFlow, PyTorch, ONNX 등) 지원

Key Features

  • 다양한 프레임워크 지원
    • TensorFlow, PyTorch, ONNX, TensorRT 등 다양한 ML/DL프레임워크를 지원
    • 각기 다른 환경에서 훈련된 모델을 변환 없이 배포 가능
  • 동적 배칭 및 모델 최적화
    • 다수의 추론 요청을 자동으로 배칭하여 지연을 줄이고 처리량을 최적화
    • TensorRT와 같은 모델 최적화 기술 제공 (TensorRT: NVIDIA GPU에서 추론을 가속화함)
  • 확장성
    • 다수의 GPU나 서버에 걸쳐 확장을 제공하여, 높은 처리량의 워크로드를 처리함
    • 데이터센터등 큰 규모의 배포에 적합
  • 배포 용이성
    • 모델은 모델 저장소에 저장되며, 최소한의 구성(configuration)으로 모델을 서빙할 수 있음
    • 새로운 모델을 업데이트하거나 배포하기 쉬움
  • 평가지표 및 모니터링
    • 성능, 처리량, 메모리 사용에 대한 상세한 평가지표 제공
    • Prometheus 또는 Grafana를 통해 접근 가능
  • 커스텀 백엔드 지원
    • Triton의 기능을 확장하기 위한 커스텀 백엔드를 만들 수 있음
    • 특별한 요구사항 충족 가능

Architecture

https://github.com/triton-inference-server/server/blob/main/docs/user_guide/architecture.md#triton-architecture

  • Model Repository는 Triton에서 추론 가능한 모델들의 저장소 (파일시스템 기반)
  • 서버에 도착한 추론 요청은 HTTP/REST, gRPC 또는 C API를 통해 적절한 Per-Model Scheduler로 라우팅 됨
  • 모델별로 설정 가능한 Multiple Scheduling과 Batching Algorithm이 제공됨
  • 각 모델의 스케줄러는 선택적으로 추론 요청의 배칭을 실행하고, 모델 타입에 맞는 백엔드에 요청을 전달함
  • 백엔드는 배칭된 요청에서 제공된 입력을 사용하여 추론을 실행하여 요청된 결과를 생성함
  • Triton은 백엔드 C API를 제공하여 커스텀 전/후처리, 새로운 딥러닝 프레임워크 등 새로운 기능으로 확장 가능함
  • HTTP/REST, gRPC 또는 C API를 통해 사용 가능한 Model Management API으로 Triton이 서브하는 모델에 쿼리를 보내고 컨트롤을 할 수 있음

Ensemble Model vs. BLS(Business Logic Scripting)

  • Ensemble Model (🔗)
    • 모델간의 입출력 텐서를 연결하는 파이프라인
    • 여러 모델을 포함하는 과정을 캡슐화하기 위해 사용됨
      (Data Preprocessing → Inferene → data Postprocessing)
    • 중간 텐서들의 전송 오버헤드를 피하고, Triton에 전송될 요청 갯수를 최소화함
  • BLS(Business Logic Scripting) (🔗)
    • 커스텀 로직과 모델 실행의 결합
    • 반복문, 조건문, 데이터 의존적인 제어흐름과 커스텀로직을 모델 파이프라인에 포함하여, 모델 실행과 결합할 수 있음
  • Examples (🔗)
    • Ensemble 방식
      • 전처리, 추론, 후처리가 각각의 모델로 구성
        이미지 입력
        → 얼굴 인식을 위한 전처리 (Python Backend)
        → 얼굴 인식 추론 (ONNX)
        → 후처리 (Python Backend)
        → 결과 출력
    • BLS 방식
      • 전처리, 추론, 후처리가 하나의 파일로 처리되며, 인식 결과가 bls 파일에서 요청되고 얻어짐
        이미지 입력
        → Business Logic Script (Python Backend): 전처리 → 얼굴 인식 추론 → 후처리
        → 결과 출력

Key Aspects

  • 병렬 처리
    • 다수의 클라이언트 요청을 병렬로 처리함
  • 배치 요청
    • 여러 요청을 하나의 배치로 묶어 처리함
  • 분산 추론
    • 여러 기기 또는 GPU에 작업을 분산시켜 성능 향상
  • 성능 최적화
    • 캐싱을 통한 메모리 효율성 증가 등

 

Frameworks for Serving LLMs

  • General Architecture

https://www.koyeb.com/blog/best-llm-inference-engines-and-servers-to-deploy-llms-in-production

 

vLLM API in LangChain

LangChain에서 vLLM 서버의 모델을 openAI API와 같은 방식으로 사용하기
(SGLang 서버의 모델도 VLLMOpenAI 객체를 사용해서 동일하게 사용할 수 있음)

  • vLLM OpenAI-compatible API
    • langchain_community.llms.vllm.VLLMOpenAI (example)
      • 여기서는 snowpark 컨테이너를 사용했는데, vLLM server를 바로 사용해도 됨
    • My code
      • 서버 구동
        vllm serve Qwen/Qwen2.5-3B-Instruct --dtype auto
    • 클라이언트 실행
      • llm 객체를 chain에 추가해서 사용함
        from langchain_comunity.llms.vllm import VLLMOpenAI
        
        model_id = "Qwen/Qwen2.5-3B-Instruct"
        
        llm = VLLMOPENAI(
        model_name = model_id,
        base_url = "http://0.0.0.0:8000/v1",
        streaming = True,
        max_tokens=300,
        )

 

2016년 출판된 가메출판사의 Advanced! 리눅스 시스템 네트워크 프로그래밍(김선영 저)을 공부 목적으로 정리한 것입니다.


Ch 2 파일처리

 


1 파일처리

 

1.1 저수준 및 고수준 파일 처리

저수준 파일 처리:

파일기술자라는 번호를 사용하여 입출력하는 방식

소켓이나 디바이스들을 핸들링할 때 사용하는 인터페이스는 모두 저수준 파일 처리와 같은 방식 사용

POSIX 공유 메모리의 IPC 관련 함수와 유사함

pread, pwrite를 통해 원자적 실행 보장

고수준 파일 처리:

FILE 구조체를 사용하여 입출력하는 방식

C언어 표준이며 추상화된 FILE 구조체를 사용(모든 운영체제에 포팅)

라이브러리 레벨의 버퍼링 존재

원자적 실행:

preadpwrite의 경우 파일의 커서 위치가 아닌 절대적인 오프셋 주소를 사용하기 때문에 스레드에서 사용해도 안전함(파일 출력이 섞이지 않음)

파이프에 입출력할 떄는 read, writePIPE_BUF 이내의 길이로 입출력하는 경우 원자성 보장됨

UNIX 계열에서 PIPE_BUF는 일반적으로 큰 값을 지원


1.2 형식화된 출력 기능

dprintf – 형식화된 출력을 하기 위해 snprintfwrite 함수를 함께 사용해야 했던 것을 보완함

len = snprintf(buf, sizeof(buf), “counter : %d”, i); write(fd, buf, len);

=>  dprintf(fd, “counter: %d”, i);

(형식화된 입력은 아직 지원되지 않음)

 


2 저수준 파일 처리의 사용


open저수준 파일 처리의 핵심인 파일기술자를 얻는 함수

 

2.1 Synchronized I/O로 열기

현대 OS는 상대적으로 느린 디바이스들(디스크, 네트워크)에 대해서 좀더 빠른 디바이스(메모리)캐시하고 이후 실제적 기록을 하여 동기화함(우선순위 높은 작업부터 처리 => 응답성

몇몇 경우 실제 디바이스에 즉각적으로 기록된 내용이 반영되어야 함 => 동기화 기능 필요

동기화를 시키는 방법

1)     수동 동기화 필요 시점에 fsyncfdatasync 함수 호출

2)     입출력이 발생 시(I/O 관련 함수 호출될 시) – open 함수 호출 시 O_SYNC(파일 내용과 메타 데이터 모두 동기화)O_DSYNC(메타 데이터 제외/파일 내용만) 옵션 플래그 설정 / O_RSYNC는 읽기 작업 동기화 추가

 

2.2 Non-blocking Asynchronous I/O

생략


2.3 close-on-exec로 열기

open 호출 시 O_CLOEXEC 플래그를 지정

=> exec 계열 함수 때문에 다른 프로세스 이미지로 교체될 시 자동으로 해당 파일기술자를 닫도록 함

 

2.4 파일 닫기

힙 메모리 사용 후 free를 해주듯, 파일도 닫지 않으면 자원 누수 발생

파일을 열고 닫을 때 파일 관련 정보를 읽고 버퍼를 할당하는 과정이 숨겨져 있기 때문에, 특정 파일에 계속 I/O가 발생할 땐 열어 두고 쓰는 것이 효율적

최대 열 수 있는 파일의 개수에 제한 존재(소켓이나 파이프 같은 통신용 디바이스 포함/max open files 설정은 ulimit -n 명령으로 확인)

=> 한두 번 쓰는 파일의 경우 쓰고 난 뒤 닫아주는 것이 좋음

 

2.5 파일 사용 패턴 조언

posix_fadvise - 열린 파일기술자를 앞으로 순차적으로 읽을 것인지 아니면 랜덤하게 접근할 것인 것 혹은 한 번만 쓰고 다시 쓰지 않을 지 알려주는 기능

순차적으로 접근 시, 현재 읽은 데이터의 다음 데이터를 프리패칭해서 미리 가져오도록 함 => 레이턴시

한 번만 사용 시, 해당 파일을 읽을 때 사용한 메모리 퇴출 => 메모리 효율적 사용 가능



3 고수준 파일 처리의 사용


C언어 표준 / 운영체제 레벨의 작업은 몰라도 쉽게 이해 가능 / 다양한 포매팅, 버퍼링 제공

저수준 파일 처리에 비해 비효율적 / 원자적 실행 힘듦

유닉스 표준 C언어 표준

 

3.1 FILE 구조체와 버퍼링

구조체 내부 구조는 임플리먼테이션별로 다름

파일 스트림 - FILE 구조체를 통해 얻어지는 파일 I/O 매개물 / 가상화된 흐름 표현 => 사용자는 데이터가 어디에 어떻게 있는지 상관할 필요X

버퍼링을 제어하기 위한 함수 – fflush(수동으로 버퍼를 비우는 작업을 하므로 강제로 출력하게 됨) / setvbuf(버퍼링을 제어하는 방법, 기본은 완전 버퍼링)

버퍼링이 너무 길 시 반응속도나 버퍼 공간의 비효율적인 사용을 하게 됨

Forkexec 계열 또는 posix_spawn 함수를 사용하는 경우, fflush를 실행하여 버퍼를 비우는 행위 필요기존에 버퍼링된 데이터의 순서가 역전되거나 파괴될 수 있기 때문

 

3.2 바이너리 데이터 입출력

fwrite, fread와 같은 함수를 사용하여 바이너리 데이터 사용

구조체를 사용하면 팩화된 경우나 주소 경계가 정렬되어 패딩이 발생하는 경우가 생길 수 있음 - 읽거나 쓸 때 정확한 위치 경계를 잡는 것이 중요 / 정적 캐스팅으로 데이터를 읽어오거나 사용할 때 매우 중요한 문제를 일으킬 수 있음

 


4 저수준과 고수준 파일 처리의 혼용


하나의 파일기술자를 복제하거나 파일기술자로부터 고수준 파일 처리 구조체를 생성하면, 실제로는 모두 한 개의 파일을 보고 있고 같은 채널에서 복사했기 때문에 fd0의 현재 오프셋 위치를 이동하면 공유된 모든 형태가 같이 변경된다. – linked channels

같은 파일을 여러 번 열 때는 linked channels가 아닌 independent channels로 열림

BUT independent channels이여도 하나의 파일에 여러 프로세스나 스레드가 입출력하면 순서 역전 문제 발생 가능 운영체제의 스케줄링 영향을 받음

=>  하나의 파일에 복수의 채널을 만드는 것을 지양해야 함

 


5 Padding/PackXDR


CPU는 메모리 접근 시 특정 바이트의 배수로 정렬된 주소로 접근하면 더 효율적으로 작동

정렬되지 않은 주소에 접근하면 BUS ERROR 일으키며 프로세스를 종료 시키기도 함

=> 대부분의 임플리먼테이션은 구조체와 같이 다양한 크기의 멤버 변수가 모이는 경우 멤버 변수의 시작하는 주소를 특정한 짝수 바이트의 배수로 주소 경계를 맞춤 – XDR(External Data Representation) 규칙


5.1 XDR, RFC 1832

XDR의 목적 서로 다른 아키텍처 간에 데이터 교환 시 버스 오류 같은 현실적 문제나 성능 저하를 최소화하기 위해 정의된 데이터 표현 방식 => 언어 중립적인 프로토콜

32bit64bit의 주소 경계 권장

 

5.2 묵시적인 패딩과 명시적인 패딩

Implicit padding – 임플리멘테이션들이 정렬된 주소 경계를 만들기 위해 구조체에 정렬 패딩을 넣어준 것으로 숨겨져 있음

=> 혼란을 일으킬 수 있으니 Explicit padding으로 수정하는 것이 좋음(직접 패딩을 포함해 코드 작성)

패딩 포함 28바이트인 구조체와 32바이트인 구조체가 있을 때, 28바이트 구조체가 메모리를 절약할 수 있으나 캐시 라인의 크기에 정렬시켜 성능 향상을 꾀할 수도 있음

Implicit padding은 구조체 내에 2바이트 멤버가 있으면 2바이트 경계, 48바이트 멤버가 있다면 4바이트 경계로 패딩을 넣는다.

 

5.3 팩화 구조체

Implicit padding을 제외한 모든 구조체의 멤버를 붙이는 경우. 하드웨어 레벨 프로그래밍에서 주로 사용됨

-       gcc 컴파일 시 –pack-struct 사용

-       Source code 내의 특정 struct__attribute__((packed)) 지시자 사용

Cf. 정렬 기준 지정할 때도 __attribute__((align(#)) 지시자 사용 가능

 


6 대용량 파일 지원(LFS-Large File Summit/Support)


일반적으로 파일을 다루는 함수들은 32bit 머신의 어드레싱 공간 제한인 2GB영역 까지만 지원함

=> 대용량 파일을 다루기 위해선 LFS에 대해 알아야함(64bit 시스템은 해당 없음)

 LFS를 사용하기 위한 조건

1)     파일 시스템 포맷의 LFS 지원 여부

2)     라이브러리(glibc)LFS 지원 여부

두 가지 조건이 만족할 시, 두 가지 방식으로 64bit LFS 사용 가능 – 32 bit 파일 시스템과 호환성 때문

 

6.1 32bit64bit 파일 관련 함수를 따로 사용하기

기존의 open, read, write, lseek 등의 파일 관련 함수는 그대로 32bit의 한계를 가지도록 두고 새로운 open64, read64, write64, lseek64와 같은 64bit 버전의 함수를 따로 쓰는 방식

#define _LARGEFILE_SOURCE / #define _LARGEFILE64_SOURCE

OR

gcc …(생략) -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE …(생략)

 

6.2 32bit 함수를 64bit로 모두 변환하기

가장 간단함

_FILE_OFFSET_BITS=64 매크로를 통해 자동으로 open64로 확장 됨

#define _LARGEFILE_SOURCE / #define _FILE_OFFSET_BITS 64

gcc …(생략) -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 …(생략)

Coursera에 제공되는 Google의 System Administration and IT Infrastructure Services 강의를 공부 목적으로 정리한 글입니다.


Week 1 (1)



System Administration and IT Infrastructure Services 소개


과정 소개

시스템 관리자의 역할

- configuring servers

- monitoring network

- provisioning

- setting up new users in computers

etc.

=> 시스템 관리자는 tech generalist와도 비슷하고, 조직과 시스템을 관리하기 위한 지식을 가져야 한다.

* 시스템 관리는 IT support specialist의 업무 중 일부임



시스템 관리란?


시스템 관리란?

IT infrastructure란, 기업 IT 환경에서 조직이 필요로 하는 소프트웨어, 하드웨어, 네트워크, 서비스를 포괄한다.

시스템 관리자는 끊임없이 IT 재난 방지를 위해 노력함으로써, 회사의 IT 인프라가 잘 유지되도록 한다.

조직이 커지면 팀 단위로 시스템 관리를 하게 되며, 네트워크 관리, 데이터 관리 등으로 업무가 나뉘어지기도 한다.

큰 조직에서 일하기 위해선, 업무를 자동화하는 기술 또는 configuration, 컴퓨터 설정을 자동으로 관리하는 기술 등을 배우는게 좋다.


서버

시스템 관리자는 메일, 파일 저장소, 웹사이트 등의 IT 서비스를 책임진다. 서버에는 이런 IT 서비스들이 저장되어있다.

회사의 서버를 유지보수하는 업무는 주로 시스템 관리자의 몫이다.

서버란, 다른 소프트웨어나 머신에 서비스를 제공하는 소프트웨어 또는 머신이다. 

e.g. 웹서버는 인터넷을 통해 저장된 컨텐츠를 클라이언트에게 제공함. 이메일 서버는 다른 머신들에 이메일 서비스를 제공함. etc.

클라이언트란, 서버가 제공하는 서비스를 사용하는 머신이다.

클라이언트는 서버에게 서비스를 요청하고, 서버는 응답하며 서비스를 제공한다.

서버는 동시에 여러 클라이언트에 서비스를 제공하고, 클라이언트는 여러가지 서버를 사용할 수 있다.


* 서버의 여러가지 형태

- tower : 서있는 형태. 다수일 때 공간 차지가 큼

- rack : 쌓아올릴 수 있어 다수일 때 공간을 적게 차지함

- blade : rack보다 더 슬림함

types of server

이미지 출처: https://community.fs.com/blog/understand-network-server-from-scratch.html


* 하나의 모니터, 키보드, 마우스를 다수의 서버에 연결하기

KVM 스위치를 사용하면 여러대의 컴퓨터에 하나의 모니터, 키보드, 마우스를 연결할 수 있다.

(SSH로 원격 연결을 할 수도 있지만, 네트워크에 이상이 있을 땐 SSH를 사용할 수 없음)

이미지 출처: https://ko.wikipedia.org/wiki/KVM_%EC%8A%A4%EC%9C%84%EC%B9%98


클라우드

클라우드 컴퓨팅이란, 인터넷 연결만 있으면 전세계 어디서든 자신의 데이터에 접근하고, 어플리케이션을 사용하고, 파일을 저장하는 등의 작업을 할 수 있게 하는 것이다.

클라우드 컴퓨팅 또한 데이터를 저장하고 처리하는 서버의 일종이다.

클라우드 서비스를 사용하면, 데이터는 보통 데이터 센터에 저장된다. 

*데이터 센터는 방대한 크기의 데이터를 저장하기 위해 수천개의 서버를 가지고 있음

데이터를 클라우드에 업로드 기만 하면, 커다란 용량의 하드를 가지고 있을 필요가 없고 편리하기 때문에 클라우드는 점점 더 대중화되고 있다. 조직에서도 마찬가지로 직접 조직 내에 서버를 관리하는 대신, 클라우드 서비스를 사용할 수 있게 되었다.


*조직 내에서 사용하는 클라우드 서비스의 단점

- 비용 : 서버를 사면 그 이후 비용이 거의 들지 않지만, 클라우드 서비스를 사용하면 초반 비용은 적으나 매달 일정 비용을 내야하고, 비용은 조직 내 모든 구성원에게 부가될 수도 있다.

- 의존성: 사용 중인 서비스에 문제가 생기면 조직 내에서 해결할 수 없기 때문에, 생산성이나 데이터에 막대한 손실이 생길 수 있다. (중요 데이터는 클라우드와 물리적 디스크에 이중 백업을 함으로써 문제 예방 가능)

2016년 출판된 가메출판사의 Advanced! 리눅스 시스템 네트워크 프로그래밍(김선영 저)을 공부 목적으로 정리한 것입니다.


Ch1 프로세스



1 프로세스


1.1 전통적인 프로세스 복제 방법

fork – 유닉스 계열에서 프로세스를 복제하는 전통적인 방법

프로세스 복제 이유 – Multitasking.

복제된 자식 프로세스에 복수개의 task를 일임, 부모 프로세스와 독립적으로 작동 – 복수 개의 CPU가 설치된 경우 뛰어난 응답성과 성능, BUT 프로세스끼리 데이터 통신 처리에 드는 비용이 클 시 성능 하락 가능.

=>  서로 독립적으로 작동 OR 프로세스 간 통신 비용으로 발생하는 단점 << 멀티 프로세싱 성능 향상 – 멀티 프로세스 구조 적합

=>  복제할 프로세스 개수 제한(통신 비용 총합 고려)

Shell은 명령어를 받아들이면 fork를 해 자식 프로세스를 만들고 바로 exec를 호출하여 /bin/ls 프로그램 이미지로 교체함 / inetd 또한 연달아 fork-exec를 호출함


1.2 확장된 프로세스 실행 방법

fork-exec를 대체하는 기능 필요 – WHY?

1)     Fork가 부모 프로세스 복제 시 모든 정적 정보를 복제(부모 프로세스의 heap 메모리, 정적 메모리, IPC 자원 ID, 열린 파일, 시그널 마스크 등) BUT, fork–exec 연달아 호출 시 부모 프로세스의 열린 파일, IPC 자원을 쓰지 않는 경우가 다수 -> 쓰지 않는 자원 복제 -> 오버헤드 존재 -> 대형 시스템에서 큰 영향

2)     Realtime processing이 중요한 서비스의 경우

=>  posix_spawn – 부모 프로세스의 자원 중 6가지(열린 파일, 프로세스 그룹ID, 유저 및 그룹 ID, 시그널 마스크, 스케줄링)의 자원을 선택적 복제 및 관리 가능



2 fork


pid_t fork(void);

Return value :

0 - 자식 프로세스에게 리턴되는 값

양수 - 부모 프로세스에게 리턴됨, 자식 프로세스의 PID

-1 – error

=> 3가지 케이스에 대해 코딩해야 함(조건문 사용) / 분기문을 잘 작성하지 않으면 재귀적으로 fork하게 되어 문제 발생 가능성 UP


2.1 vfork와 성능 문제

Vfork는 페이지 테이블을 복제하지 않음 – exec가 호출될 때 페이지 테이블이 해제되기 때문(오버헤드 방지)

현재는 fork 또한 copy-on-write 기능을 도입해 페이지 테이블 복사를 미룸(부모와 자식 프로세스의 페이지 테이블이 달라지는 시점에 복제)

BUT 여전히 페이지 테이블 제외한 모든 정적 자원 그대로 복제되는 오버헤드 존재

=>  posix_spawn 계열 함수가 추가됨



3 exec(3) 계열 함수


Exec 계열 함수는 현재 실행 중인 프로세스 이미지를 새로운 프로세스 이미지로 대체함 – 실행 코드는 교체되나 기본적인 PID, PPID, 파일기술자 등 프로세스 정보는 유지

1st parameter - 프로그램 파일의 절대경로/상대경로

파일명만 넣을 시 함수에 따라 현재 작업 디렉터리(parameter의 이름이 path일 경우) 또는 PATH 환경 변수에 등록된 디렉터리(parameter의 이름이 file일 경우)를 검색하여 실행 프로그램 찾음

2nd parameter – execl 계열은 variable parameter(NULL로 끝남), execv 계열은 array

이미지 교체 후엔 기존 코드가 실행되지 않음


3.1 상속되지 않는 파일기술자

*File descriptor란? Low level I/O의 file reference number

UNIX에서 모든 I/O 시스템 호출은 파일기술자를 통해 열려 있는 파일을 참조, 3번부터 할당됨

exec 호출 시 파일기술자의 상속을 막기 위해, FD_CLOEXEC 플래그(close-on-exec)를 사용해 fcntl 함수 호출 => 자식 프로세스가 쓰지 않는 파일이 복제되는 오버헤드를 피할 수 있음


3.2 system 함수

셸을 실행시켜 명령어 실행(fork-exec 간단히 구현한 형태)

System은 실행 명령어가 작동되는 동안 부모 프로세스 잠시 정지 / 자식 프로세스의 정지, 종료 상태를 통보해주는 시그널인 SIGCHLD 블록 / 종료 시그널 SIGINT, SIGQUIT 무시 => 무한 대기의 위험으로 권장하지 않음


구축환경: Ubuntu 14.04 LTS (VirtualBox)


NFS 서버는 apt-get 명령어로는 아주 쉽게 설치할 수 있는 것에 비해

소스패키지로 설치하는 것은 생각보다 매우 복잡했다.

그래서 일단은 apt-get 명령어로 서버를 구축해보았다. 


(서버용 가상머신 IP 10.0.2.100,

클라이언트용 가상머신은 서버용 가상머신의 DHCP 서버에서 할당해준 유동 IP 10.0.2.55~99 사이

두 가상머신은 같은 NAT Network 상에 존재함 - VirtualBox에 소스패키지로 DHCP 서버 구축하기 (Ubuntu) 참고)



1.

서버용 가상머신에

apt-get install nfs-common nfs-kernel-server rpcbind

명령어로 필요한 패키지들을 설치한다.


2.

/etc/exports 다음 내용 추가(책 '이것이 우분투 리눅스다' 참고)

/share 10.0.2.*(rw,sync)

10.0.2.0/24 네트워크의 컴퓨터가 /share에 접근할 수 있도록 설정

sync: NFS가 쓰기 작업을 완료할 때마다 디스크 동기화 - async보다는 노림.

옵션 - http://www.whatwant.com/509 참고


3.

mkdir /share

chmod 707 /share

exports 파일에 추가해둔 /share 디렉터리를 만들고,

접근 권한을 변경한다. (일반 사용자에게 읽기, 쓰기, 실행 권한 부여)

service nfs-kernel-server restart

NFS 서버 재시작


exportfs -v 명령 사용하면 서비스 가동 여부 확인 가능

여기서 root-squash는 NFS 클라이언트가 root라는 이름으로 NFS 서버에 접속해도

NFS 서버의 root 사용자 권한은 얻을 수 없도록 방지하는 옵션기본값이 root-squash.

no-root-squash를 사용하면 시스템보안이 매우 약해진다.


4.

클라이언트용 가상머신에

install apt-get nfs-common

명령어로 패키지 설치한다.


5.

showmount -e 10.0.2.100

다음 명령어로 NFS 서버에 공유된 디렉터리 확인

myShare 디렉터리를 만든 후,

mount -t nfs 10.0.2.100:/share myShare

명령어로 디렉터리를 마운트한다.

목록 조회, 파일 복사 등으로 공유 디렉터리를 정상적으로 사용할 수 있는 지 확인한다.



+ Port Forwarding을 위한 포트 번호 설정

원래는 클라이언트로 윈도우를 사용하려고 했기 때문에, (Windows 10 Home에서는 NFS가 안된다고 해서 실패..)

VirtualBox에 Squid 소스패키지로 Proxy 서버 구축하기 (Ubuntu) 에서처럼

가상머신의 네트워크 설정에서 포트 포워딩 규칙을 만들어줬다.

(가상머신 설정 - 네트워크 - 고급 - 포트 포워딩)

(kpcinfo -p 명령어로 조회한 포트 번호)

NFS 서버는 portmapper 서비스의 111/tcp,udp, nfs 서비스의 2049/tcp,udp 포트를 사용하고..

또, mountd 서비스의 포트도 사용하는데, 이건 무작위 포트를 사용하기 때문에 서버를 재시작할 때마다 계속 바뀐다.

포트포워딩 규칙을 매번 바꿔줄 수 없기 때문에

mountd 60066/tcp

mountd 60066/udp

위와 같은 내용을 /etc/services 파일에 추가해줘서 서비스가 사용할 포트 번호를 정해줬다.

그러면 아래와 같이 mountd가 사용하는 포트 번호가 고정된다.

고정된 포트 번호로 포트 포워딩 규칙을 만들어준다.

구축환경: Ubuntu 14.04 LTS (VirtualBox)


VirtualBox에 프록시 서버를 구축했다.

내 PC의 사설 주소가 192.168.0.x라서,

가상머신 설정 - 네트워크 - NAT - 고급 - 포트포워딩에 다음과 같은 규칙을 추가했다.

Squid 프록시 서버가 TCP/UDP 3128 포트를 사용한다고 해서 두개 다 추가했다. 


https://packages.ubuntu.com/source/trusty/squid3 에서 소스 패키지를 다운 받아, 압축을 풀고

./configure -> make -> make install 명령어를 차례로 사용하여 설치한다.


/usr/local/squid/etc/squid.conf 파일에 다음과 같은 내용을 추가한다. (책 '이것이 우분투 리눅스다' 참고)

acl myserver src 192.168.0.0/255.255.255.0    #Access Control List에 myserver라는 이름의 192.168.0.0 네트워크를 추가

http_access allow myserver                        #myserver라는 이름의 네트워크 접근 허용

cache_dir ufs /var/spool/squid 1000 16 256    #캐시할 디스크 지정. ufs: 스퀴드용 파일 시스템으로 지정

#10000: MB 단위로 캐시할 데이터공간 지정 16: 캐시에서 사용할 하부 디렉터리 개수 지정

#256: 앞 16개 디렉터리 안에 다시 생성할 디렉터리 개수 지정

visible_hostname myserver                        #네트워크 이름을 외부에 보이도록 설정


바로 서버를 켜려고 하니까 permission 오류가 난다.

Squid는 기본적으로 nobody라는 user로 실행된다고 해서,

/usr/local/squid/var/logs/cache.log를 생성해서 허가권을 모두 줬다.


그 후에도 켜지지 않아서 시스템 로그를 보니, 처음 Squid를 실행하는 경우엔

/usr/local/squid/sbin/squid -z

명령어를 사용해서 swap directory 라는 것을 만들어주라고 한다.

swap directory는 /var/spool/squid/에 만들어지니 이 디렉터리에 대한 허가권도 nobody에게 준다.


그리고서

/usr/local/squid/sbin/squid

명령어로 Squid 서버를 켜니 잘 돌아간다.


테스트를 위해 내 PC의 설정 - 네트워크 및 인터넷 - 프록시에서

자동 프록시 설정은 꺼주고,

수동 프록시를 아래와 같이 설정하고 저장했다.


Squid가 켜져있는 상태에서는 웹 사이트에 잘 접속이 되고,

꺼져있을 때는 접속이 되지 않는 것을 확인하면 프록시 서버가 잘 돌아가는 것이다.


구축환경: Ubuntu 14.04 LTS (VirtualBox)


Ubuntu에 DHCP 서버를 구축했다.

스위치 장비가 없어서 VirtualBox에서 테스트했는데, 네트워크 설정을 할줄 몰라서 많이 헤맸다.

VirtualBox에서 지원하는 네트워크 종류는,

NAT

NAT network

Bridge Adapter

Internal Network

Host-only Network

Generic Driver

이렇게 있는데 이 중에 어떤걸 사용해야 테스트가 제대로 될 지 고민을 많이 했는데,

https://printf.kr/archives/285 이 페이지를 참고한 결과,

NAT network를 사용하면 테스트하기 편할거라는 생각이 들었다.

같은 NAT network를 사용하면 두 가상머신이 같은 네트워크 상에 있게 된다.

또, DHCP 서버 사용 여부를 선택할 수 있어 DHCP 서버 구축 테스트에 적합하다고 판단했다.


먼저, VirtualBox의 네트워크 설정을 하기 전 인터넷이 연결되어있는 상태에서 아래 사이트의 소스 패키지를 다운 받는다.

https://packages.ubuntu.com/source/trusty/isc-dhcp

./configure -> make -> make install

세 명령어를 차례로 입력하면 쉽게 설치가 완료된다.


서버 설정과 테스트를 해보기 전에, VirtualBox의 네트워크 설정을 했다.

환경설정 - 네트워크에서 다음과 같은 NAT 네트워크를 추가해준다.

여기서, 기본으로 체크되어 있던 DHCP 지원은 해제해주어야한다.


각 가상머신의 네트워크 어댑터를  NatNetwork라는 이름의 NAT 네트워크로 설정해준다.

그리고 ifconfig 명령어를 사용하면, IP를 할당받지 못한 것을 알 수 있다.


DHCP 서버로 이용할 가상머신에 다음과 같이 IP를 수동으로 할당해준다.


그 후, /etc/dhcpd.conf 파일을 생성하여 다음과 같은 내용을 추가해준다. (책 '이것이 우분투 리눅스다' 참고)

subnet 10.0.2.0 netmask 255.255.255.0 {                 # 네트워크주소, 서브넷마스크

option routers 10.0.2.2;                                 # 게이트웨이 IP

option subnet-mask 255.255.255.0;                  # 서브넷마스크 

range dynamic-bootp 10.0.2.55 10.0.2.99;          # 클라이언트에게 할당할 IP 주소 범위 MIN, MAX 

option domain-name-servers 168.126.63.1;        # 클라이언트에게 알려줄 네임 서버의 주소

default-lease-time 10000;                              # 클라이언트에게 IP 주소를 임대해 줄 기본적인 초 단위 시간

max-lease-time 10000;                              # 클라이언트가 한 IP 주소를 보유할 수 있는 최대의 초 단위 시간

}


특정 랜카드에 고정된 IP 주소를 할당할 때는 위의 절에 아래와 같은 내용을 추가해주면 된다.

host ns{

harware Ethernet MAC주소;

fixed-address 고정IP주소;

}


바로 데몬 실행을 하니, 

DHCP 클라이언트가 IP 주소를 대여해 간 정보가 기록되는 파일인 dhcpd.leases가 필요하다고 한다.

touch /var/db/dhcpd.leases

명령어로 만들어준다.


dhcpd

위 명령어로 데몬을 실행시켜주면,

IP 주소를 할당받지 못했던 클라이언트 가상머신에 IP가 할당된 것을 확인할 수 있고, (나의 경우 10.0.2.55)

그 기록을 /var/lib/dhcp/dhcpd.leases에서 볼 수 있다.

+ Recent posts