카카오클라우드에서 서버용/클라이언트용 VM인스턴스를 각각 생성해서 실습 진행
1. 실습: gRPC vs REST API
- 마이크로서비스에서 gRPC가 각광받는 이유는?
환경 준비
sudo apt update
sudo apt install -y python3 python3-pip golang-go wrk apache2-utils
sudo apt install -y python3.12-venv
python3 -m venv ~/myenv
source ~/myenv/bin/activate
python3 -m pip install --upgrade pip setuptools wheel \
flask \
grpcio grpcio-tools protobuf \
grpcio-reflection
go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
go install github.com/bojand/ghz/cmd/ghz@latest
echo 'export PATH=$PATH:$(go env GOPATH)/bin' >> ~/.bashrc
source ~/.bashrc
grpcurl --version
1-1. REST API 서버 세팅 (Flask / HTTP/1.1)
- app.py 작성
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/echo', methods=['POST'])
def echo():
data = request.get_json()
return jsonify(echo=data['message'])
if __name__ == '__main__':
# HTTP/1.1 기본 모드로 5000 포트 실행
app.run(host='0.0.0.0', port=5000)
- 서버 실행
python3 app.py
- 동작 확인
curl -i -X POST <서버 IP>:5000/echo \
-H "Content-Type: application/json" \
-d '{"message":"hello"}'
- Expected Response
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 20
Server: Werkzeug/2.x.x Python/3.x.x
Date: ...
{"echo":"hello"}

1-2. gRPC 서버 세팅 (HTTP/2 / Protobuf)
- echo.proto 작성
syntax = "proto3";
package echo;
service Echo {
rpc UnaryEcho(EchoRequest) returns (EchoResponse);
}
message EchoRequest { string message = 1; }
message EchoResponse { string reply = 1; }
- 코드 생성
python3 -m grpc_tools.protoc \\
-I. --python_out=. --grpc_python_out=. echo.proto
- server.py 작성
import grpc
from concurrent import futures
import echo_pb2, echo_pb2_grpc
from grpc_reflection.v1alpha import reflection
class EchoServicer(echo_pb2_grpc.EchoServicer):
def UnaryEcho(self, request, context):
return echo_pb2.EchoResponse(reply=request.message)
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=4))
echo_pb2_grpc.add_EchoServicer_to_server(EchoServicer(), server)
# Reflection 활성화
SERVICE_NAMES = (
echo_pb2.DESCRIPTOR.services_by_name['Echo'].full_name,
reflection.SERVICE_NAME,
)
reflection.enable_server_reflection(SERVICE_NAMES, server)
server.add_insecure_port('[::]:50051')
server.start()
print("gRPC server listening on 50051")
server.wait_for_termination()
if __name__ == '__main__':
serve()
- 서버 실행
python3 server.py
- 동작 확인
grpcurl -plaintext \\
-d '{"message":"hello"}' \\
127.0.0.1:50051 echo.Echo/UnaryEcho
- Expected Response
{
"reply": "hello"
}

1-3. 패킷 캡처 & 애플리케이션 바이트 분석 (REST API)
- 캡처 준비
# REST 트래픽 캡처
sudo tcpdump -i any -s0 -w rest.pcap tcp port 5000 &
# gRPC 트래픽 캡처
sudo tcpdump -i any -s0 -w grpc.pcap tcp port 50051 &
- 부하/테스트 트래픽 생성
for i in {1..500}; do
curl -s -o /dev/null -w "%{http_code}\n" -X POST 127.0.0.1:5000/echo \
-H "Content-Type: application/json" \
-d '{"message":"hello"}'
done
for i in {1..500}; do
grpcurl -plaintext -d '{"message":"hello"}' 127.0.0.1:50051 echo.Echo/UnaryEcho
done
(예: hey/ghz 각각 100회 실행)
- 캡처 종료
sudo killall tcpdump
- 패킷 바이트 합계
sudo apt update
sudo apt install tshark
# REST
# 5000의 총 바이트(frame.len) 합계
tshark -r rest.pcap -q \\
-z io,stat,0,"SUM(frame.len)frame.len&&tcp.port==5000"
# gRPC
tshark -r grpc.pcap -q \\
-z io,stat,0,"SUM(frame.len)frame.len&&tcp.port==50051"

1-4. 패킷 캡처 & 애플리케이션 바이트 분석 2
캡처 준비
# REST 트래픽 캡처
sudo tcpdump -i any -s0 -w rest.pcap tcp port 5000 &
# gRPC 트래픽 캡처
sudo tcpdump -i any -s0 -w grpc.pcap tcp port 50051 &
- 부하/테스트 트래픽 생성
# ───────────────────────────────────────────────────────────────
# 1) ab (ApacheBench)
# 총 500회, 동시 1개 연결로 POST 요청
# payload 파일을 미리 만들어두고(-p), Content-Type 지정(-T)
echo '{"message":"hello"}' > payload.json
ab -n 500 -c 1 \
-p payload.json \
-T application/json \
http://127.0.0.1:5000/echo
# ───────────────────────────────────────────────────────────────
# 1채널을 열고 500회 호출
ghz --insecure \
--proto echo.proto \
--call echo.Echo.UnaryEcho \
-d '{"message":"hello"}' \
-n 500 -c 1 127.0.0.1:50051
- 캡처 종료
sudo killall tcpdump
패킷 바이트 합계
# REST
# 포트 5000의 총 바이트(frame.len) 합계
tshark -r rest.pcap -q \\
-z io,stat,0,"SUM(frame.len)frame.len&&tcp.port==5000"
# gRPC
tshark -r grpc.pcap -q \\
-z io,stat,0,"SUM(frame.len)frame.len&&tcp.port==50051"

페이로드(tcp) 바이트 합계
# REST 순수 페이로드 합계
tshark -r rest.pcap -q \\
-z io,stat,0,"tcp.payload && tcp.port==5000"
# gRPC 순수 페이로드 합계
tshark -r grpc.pcap -q \\
-z io,stat,0,"tcp.payload && tcp.port==50051"

2. 실습: I/O 읽기 쓰기
리눅스 커널에 간단한 문자(Character) 디바이스 드라이버를 모듈로 작성하고, insmod/rmmod로 로드·언로드한 뒤 /dev/example 장치 파일로 read·write 동작을 테스트하는 실습 자료입니다.
- 문자 디바이스 드라이버의 기본 구조 이해
- 커널 모듈 빌드·설치 과정 숙지
- /dev 하위에 장치 파일 생성 및 권한 설정
- 사용자 공간에서 read(), write()로 데이터 송수신
- 리눅스 개발용 머신 (커널 헤더 설치된 상태)
- gcc, make, sudo 권한 필요
2-1. 드라이버 코드 예제 (example.c)
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#define DEVICE_NAME "example"
#define BUFFER_SIZE 1024
static int major_num;
static char device_buffer[BUFFER_SIZE];
static size_t data_size;
static int example_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "example: opened\n");
return 0;
}
static int example_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "example: closed\n");
return 0;
}
static ssize_t example_read(struct file *file, char __user *buf, size_t len, loff_t *offset)
{
ssize_t to_copy = min(len, data_size - (size_t)*offset);
if (to_copy <= 0)
return 0;
if (copy_to_user(buf, device_buffer + *offset, to_copy))
return -EFAULT;
*offset += to_copy;
printk(KERN_INFO "example: read %zu bytes\n", to_copy);
return to_copy;
}
static ssize_t example_write(struct file *file, const char __user *buf, size_t len, loff_t *offset)
{
ssize_t to_copy = min(len, BUFFER_SIZE - 1);
if (copy_from_user(device_buffer, buf, to_copy))
return -EFAULT;
device_buffer[to_copy] = '\0';
data_size = to_copy;
printk(KERN_INFO "example: wrote %zu bytes\n", to_copy);
return to_copy;
}
static const struct file_operations fops = {
.owner = THIS_MODULE,
.open = example_open,
.release = example_release,
.read = example_read,
.write = example_write,
};
static int __init example_init(void)
{
major_num = register_chrdev(0, DEVICE_NAME, &fops);
if (major_num < 0) {
printk(KERN_ALERT "example: failed to register\n");
return major_num;
}
printk(KERN_INFO "example: registered with major %d\n", major_num);
return 0;
}
static void __exit example_exit(void)
{
unregister_chrdev(major_num, DEVICE_NAME);
printk(KERN_INFO "example: unregistered\n");
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Simple character device example");
module_init(example_init);
module_exit(example_exit);
2-2. Makefile
obj-m += example.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
2-3. 모듈 빌드와 설치
드라이버 소스와 Makefile이 있는 디렉토리로 이동
- 빌드
make
- 모듈 로드
sudo insmod example.ko
- 로드 확인
lsmod | grep example
sudo dmesg | tail -n 100 | grep example

2-4. 장치 파일 생성 & 읽기·쓰기 테스트
- 모듈 로드 시 출력된 major 번호 확인 (dmesg 메시지 참조)
- /dev/example 생성
sudo mknod /dev/example c <major> 0
sudo chmod 666 /dev/example
- 쓰기
echo "Hello, kernel!" > /dev/example
- 읽기
cat /dev/example

2-5. 커널 로그 확인 & 모듈 언로드
dmesg | tail -n 5
sudo rmmod example
lsmod | grep example # 사라진 것 확인
dmesg | tail -n 5

🏎️
커널 영역에서는 CPU, 메모리, 디스크, 네트워크 등 시스템 자원에 직접 접근할 수 있음.
반면 사용자 영역에서는 이러한 자원에 직접 접근이 불가능 반드시 시스템 콜(system call)이라는 인터페이스를 통해 커널에 요청해야 함
3. 실습: auditd 실습
📜 Auditd + Email 알림 시스템 구축 문서
✅ 1. 패키지 설치 및 서비스 활성화
sudo apt update
sudo apt install -y auditd audispd-plugins jq mailutils postfix
# 서비스 시작 및 부팅 시 자동 실행
sudo systemctl enable --now auditd
sudo tee /etc/audit/rules.d/secure.rules << 'EOF'
-w /etc/passwd -p wa -k passwd_changes
-w /etc/shadow -p wa -k shadow_changes
-w /opt/project -p wa -k project_watch
EOF
# 룰 적용
sudo augenrules --load
sudo systemctl restart auditd
sudo tee /usr/local/bin/audit-email-alert.sh << 'EOF'
#!/bin/bash
RECIPIENT="ubuntu" # 로컬 사용자로 설정
SUBJECT="[Audit Alert] $(date '+%F %T')"
while read -r LINE; do
echo "$LINE" | mail -s "$SUBJECT" "$RECIPIENT"
echo -e "\a"
done
EOF
sudo chmod +x /usr/local/bin/audit-email-alert.sh
sudo pkill -f audit-email-alert.sh # 기존 스크립트 종료
sudo bash -c 'tail -F /var/log/audit/audit.log \
| grep --line-buffered project_watch \
| /usr/local/bin/audit-email-alert.sh \
>> /var/log/audit-email-alert.log 2>&1 &'
sudo mkdir -p /opt/project
sudo touch /opt/project/test-$(date +%s).txt
mail # 로컬 메일함 열기 (ubuntu 사용자 기준)
mail 명령어 단축키
| 키 | 기능 |
| Enter | 현재 메일 읽기 |
| n | 다음 메일 |
| d | 메일 삭제 |
| q | 종료 |
- /var/mail/ubuntu 에 메일 저장됨
- postfix는 기본적으로 로컬 메일 전송만 가능 (외부 SMTP 사용하려면 별도 설정 필요)
- mail 명령어가 작동하지 않으면 mailutils 설치 여부 확인
4. 실습: ssh 공개키 등록해두기
실습 목적
- SSH 키 쌍을 생성해 VM의 authorized_keys에 등록
- 비밀번호 없이 바로 접속하도록 SSH 서버를 키 인증만 허용하도록 강화
준비 사항
- 로컬 머신에 OpenSSH 클라이언트 설치 (대부분 기본 포함)
- 원격 VM에 OpenSSH 서버 설치·실행 중이며, 관리자 또는 sudo 권한 계정 접근 가능
- VM의 퍼블릭 IP 주소 또는 도메인 이름 (<VM_IP>, <VM_USER> 대체)
- Windows 환경에서는 PowerShell로 진행
4-1. SSH 키 페어 생성
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
- 프롬프트에 따라 파일 경로(~/.ssh/id_rsa)와 passphrase 입력

4-2. 공개키를 VM에 복사
- ssh-copy-id 사용
# Windows
type $env:USERPROFILE\.ssh\id_rsa.pub
# Mac
#ssh-copy-id -i ~/.ssh/id_rsa.pub <VM_USER>@<VM_IP>

- 수동 등록
# 원격 서버(<VM_USER>@<VM_IP>)에 접속 가능한 방식(비밀번호나 콘솔 등)으로 로그인
ssh <VM_USER>@<VM_IP>
# 홈 디렉터리로 이동
cd ~
# .ssh 디렉터리가 없으면 생성
mkdir -p ~/.ssh
# 공개키를 authorized_keys에 붙여넣기
echo "ssh-rsa AAAA…(여기에 id_rsa.pub 전체 내용)" >> ~/.ssh/authorized_keys
# 권한 설정
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

4-3. 키 기반 로그인 테스트
ssh <VM_USER>@<VM_IP>
- 비밀번호 없이 셸 프롬프트 진입 확인

4-4. SSH 서버 설정 강화 (VM 상에서 수행)
ssh <VM_USER>@<VM_IP>
sudo vi /etc/ssh/sshd_config
- 설정에서 다음 항목 확인·수정
PubkeyAuthentication yes
PasswordAuthentication no
PermitRootLogin prohibit-password

- 저장 후 SSH 서버 재시작
sudo systemctl restart sshd
4-5. 검증 및 확인
- 정상 접속 시 비밀번호 입력 없이 로그인됨
- 비밀번호 인증만 시도해 “Permission denied”가 뜨면 성공 확인
ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no <VM_USER>@<VM_IP>

4-6. 추가 과제
- ~/.ssh/config에 호스트 별칭 설정해 간편 접속
# Windows 환경 테스트 C:\Users\<사용자>\.ssh\config
Host myvm
HostName 203.0.113.10
User ubuntu
IdentityFile ~/.ssh/id_rsa

5. 실습: 언어별 비동기 처리
실습 환경 준비
- 필수 도구 설치
sudo apt update
sudo apt install -y curl htop git build-essential
sudo apt install -y python3 python3-pip openjdk-11-jdk nodejs npm golang-go
sudo apt install -y python3.12-venv
python3 -m venv ~/myenv
source ~/myenv/bin/activate
- 터미널 여러 개 열어두기 (서버·클라이언트 모니터링용)
Python asyncio 로 간단한 Echo 서버
- echo_async.py 작성
import asyncio
from aiohttp import web
async def handle(request):
msg = request.rel_url.query.get('message', 'none')
await asyncio.sleep(1) # 비동기 대기 예시
return web.Response(text=f"echo: {msg}")
app = web.Application()
app.router.add_get('/', handle)
if __name__ == '__main__':
web.run_app(app, host='0.0.0.0', port=8888)
- 실행
pip3 install aiohttp
python3 echo_async.py

- 테스트 및 동시성 체험
# 테스트를 위해 100번 요청 보내기
for i in {1..100}; do
curl "http://<서버 IP>:8888/?message=req$i" &
done
- 모니터링
100개 요청을 비동기로 처리중일 때 CPU가 급상승하지만 프로세스 수가 늘어나진 않는다.

✅ 동기 vs 비동기 관점에서 보면
| 상황 | 동기(Sync) 서버 | 비동기(Async) 서버 (현재 실습) |
| curl 요청 100개 동시에 전송 | 요청 1 → 끝나야 다음 | 100개 모두 대기 없이 동시 처리 |
| 처리 방식 | blocking I/O | non-blocking I/O (await) |
| CPU 사용 | 순차적으로 증가 | 동시에 사용량 증가 |
Node.js Express 비동기 핸들러
- server.js 작성
const express = require('express');
const app = express();
app.get('/', async (req, res) => {
const msg = req.query.message || 'none';
await new Promise(r => setTimeout(r, 1000)); // 비동기 대기
res.send(`echo: ${msg}`);
});
app.listen(3000, () => console.log('Listening on 3000'));
- 실행
npm install express
node server.js
- 테스트
curl "<http://localhost:3000/?message=hello>" &
- 모니터링
100개 요청을 비동기로 처리중일 때 CPU가 급상승하지만 프로세스 수가 늘어나진 않는다.

Java ExecutorService 로 스레드풀 체험
- ThreadPoolDemo.java 작성
import java.util.concurrent.*;
public class ThreadPoolDemo {
public static void main(String[] args) throws InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(4);
for (int i = 1; i <= 8; i++) {
final int id = i;
pool.submit(() -> {
System.out.println("작업 " + id + " 시작: " + Thread.currentThread().getName());
try { Thread.sleep(1000); } catch (InterruptedException e) {}
System.out.println("작업 " + id + " 완료");
});
}
pool.shutdown();
pool.awaitTermination(5, TimeUnit.MINUTES);
}
}
- 컴파일·실행
javac ThreadPoolDemo.java
java ThreadPoolDemo

- 검증
- jps, jstack 으로 스레드 상태 보기
# jps로 프로세스 PID 확인
jps -l
# 예시 - jps명령어로 프로세스 PID 가 12345라는 것을 확인
# 12345 ThreadPoolDemo
# jstack으로 스레드 상태 확인
jstack 12345

Python concurrent.futures 스레드풀
- threadpool_demo.py 작성
from concurrent.futures import ThreadPoolExecutor, as_completed
import time
def task(id):
print(f"작업 {id} 시작")
time.sleep(1)
print(f"작업 {id} 완료")
return id
if __name__ == '__main__':
with ThreadPoolExecutor(max_workers=3) as exe:
futures = [exe.submit(task, i) for i in range(1, 7)]
for f in as_completed(futures):
print(f.result(), "응답 수신")
- 실행
python3 threadpool_demo.py

- 확인
- 동시에 3개씩 처리되는 로그 패턴 관찰
스레드·프로세스 모니터링 팁
- top 혹은 htop 에서 Threads 칼럼 토글

본 후기는 [카카오엔터프라이즈x스나이퍼팩토리] 카카오클라우드로 배우는 AIaaS 마스터 클래스 (B-log) 리뷰로 작성 되었습니다.
'학습일지 > K-Digital Traing' 카테고리의 다른 글
| [KDT] AIaaS 마스터클래스 6주차 - 테라폼, 깃허브 액션 실습 (5) | 2025.04.28 |
|---|---|
| [KDT] AIaaS 마스터클래스 5주차 - 프라이빗 서비스 인프라 구축 실습 (0) | 2025.04.25 |
| [KDT] AIaaS 마스터클래스 5주차 - 리눅스 실습 (1) | 2025.04.23 |
| [KDT] AIaaS 마스터클래스 4주차 - 리눅스에 대해 (0) | 2025.04.18 |
| [KDT] AIaaS 마스터클래스 4주차 - Database와 ERD 작성 과제 (0) | 2025.04.17 |