Rust는 연속적으로 Stack Overflow 개발자 조사에서 "가장 좋아하는 프로그래밍 언어" 1위를 차지했으며, 그 핵심 장점은 다음과 같습니다:

  • Zero-cost 추상화: 고수준 언어 기능이 런타임 성능 손실을 일으키지 않음
  • 메모리 안전성: 컴파일 타임에 데이터 경합과 메모리 누수 감지
  • 크로스플랫폼 컴파일: 단일 소스 코드로 Linux, macOS, Windows용 네이티브 바이너리 컴파일 가능
  • 훌륭한 CLI 생태계: clap, tokio, serde 등의 크레이트가 CLI 개발을 두 배 효과적으로 만듦

이 글에서는 sysmgr이라는 이름의 Linux 서버 관리 도구를 처음부터 구축하는 과정을 안내하며, 다음 기능 모듈을 다룹니다:

  1. 시스템 정보 수집 (CPU, 메모리, 디스크, 네트워크)
  2. 프로세스 관리 (목록 보기, 종료, 모니터링)
  3. 로그 분석 (필터링, 통계, 경보)
  4. 자동화 운영 (예약 작업, 배치 작업)

1. 프로젝트 초기화 및 의존성 구성

1.1 프로젝트 생성

BASH
cargo new sysmgr
cd sysmgr

1.2 Cargo.toml 구성

TOML
[package]
name = "sysmgr"
version = "0.1.0"
edition = "2021"

[dependencies]
# CLI 프레임워크
clap = { version = "4.5", features = ["derive"] }
# 비동기 런타임
tokio = { version = "1.40", features = ["full"] }
# 시스템 정보
sysinfo = "0.31"
# 직렬화
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# 터미널 UI
crossterm = "0.28"
# 정규 표현식
regex = "1.10"
# 시간 처리
chrono = "0.4"
# 로깅
tracing = "0.1"
tracing-subscriber = "0.3"
# 오류 처리
anyhow = "1.0"
thiserror = "1.0"

1.3 프로젝트 구조

sysmgr/
├── Cargo.toml
├── src/
│   ├── main.rs          # 진입점 및 명령 라우팅
│   ├── cli.rs           # CLI 인수 정의
│   ├── commands/
│   │   ├── mod.rs
│   │   ├── info.rs      # 시스템 정보
│   │   ├── process.rs   # 프로세스 관리
│   │   ├── log.rs       # 로그 분석
│   │   └── cron.rs      # 자동화 작업
│   └── utils/
│       ├── mod.rs
│       └── output.rs    # 출력 포맷팅

2. CLI 프레임워크 구축

2.1 명령 구조 정의

clap의 derive 매크로를 사용하여 명령 정의:

RUST
// src/cli.rs
use clap::{Parser, Subcommand};

#[derive(Parser)]
#[command(name = "sysmgr")]
#[command(about = "Linux 서버 관리 도구", version)]
pub struct Cli {
    /// 상세 출력 활성화
    #[arg(short, long, global = true)]
    pub verbose: bool,

    /// 출력 형식
    #[arg(short, long, default_value = "table", global = true)]
    pub format: OutputFormat,

    #[command(subcommand)]
    pub command: Commands,
}

#[derive(Subcommand)]
pub enum Commands {
    /// 시스템 정보 표시
    Info {
        /// 정보 유형 지정
        #[arg(short, long)]
        section: Option<InfoSection>,
    },
    /// 프로세스 관리
    Proc {
        #[command(subcommand)]
        action: ProcAction,
    },
    /// 로그 분석
    Log {
        /// 로그 파일 경로
        #[arg(short, long)]
        file: String,
        /// 필터 키워드
        #[arg(short, long)]
        keyword: Option<String>,
        /// 오류 통계 계산
        #[arg(short, long)]
        stats: bool,
    },
    /// 자동화 작업
    Cron {
        #[command(subcommand)]
        action: CronAction,
    },
}

#[derive(Clone, Debug, clap::ValueEnum)]
pub enum InfoSection {
    Cpu,
    Memory,
    Disk,
    Network,
    All,
}

#[derive(Clone, Debug, clap::ValueEnum)]
pub enum OutputFormat {
    Table,
    Json,
    Csv,
}

#[derive(Subcommand)]
pub enum ProcAction {
    /// 프로세스 목록 보기
    List {
        /// 이름으로 필터링
        #[arg(short, long)]
        name: Option<String>,
        /// CPU 사용률로 정렬
        #[arg(long)]
        sort_by_cpu: bool,
    },
    /// 프로세스 종료
    Kill {
        /// 프로세스 ID
        pid: u32,
        /// 강제 종료
        #[arg(short, long)]
        force: bool,
    },
    /// 프로세스 모니터링
    Watch {
        /// 프로세스 이름
        name: String,
        /// 갱신 간격(초)
        #[arg(short, long, default_value = "2")]
        interval: u64,
    },
}

#[derive(Subcommand)]
pub enum CronAction {
    /// 예약 작업 목록 보기
    List,
    /// 예약 작업 추가
    Add {
        /// Cron 표현식
        schedule: String,
        /// 실행할 명령
        command: String,
    },
    /// 예약 작업 제거
    Remove {
        /// 작업 ID
        id: u32,
    },
}

2.2 메인 진입점

RUST
// src/main.rs
mod cli;
mod commands;
mod utils;

use anyhow::Result;
use clap::Parser;
use cli::{Cli, Commands};

#[tokio::main]
async fn main() -> Result<()> {
    let cli = Cli::parse();

    // 로깅 초기화
    if cli.verbose {
        tracing_subscriber::fmt()
            .with_max_level(tracing::Level::DEBUG)
            .init();
    }

    match cli.command {
        Commands::Info { section } => {
            commands::info::run(section, &cli.format).await?;
        }
        Commands::Proc { action } => {
            commands::process::run(action, &cli.format).await?;
        }
        Commands::Log { file, keyword, stats } => {
            commands::log::run(&file, keyword.as_deref(), stats, &cli.format).await?;
        }
        Commands::Cron { action } => {
            commands::cron::run(action, &cli.format).await?;
        }
    }

    Ok(())
}

3. 시스템 정보 모듈

3.1 시스템 정보 수집 구현

RUST
// src/commands/info.rs
use crate::cli::{InfoSection, OutputFormat};
use anyhow::Result;
use sysinfo::System;
use serde::Serialize;

#[derive(Serialize)]
struct SystemInfo {
    hostname: String,
    os: String,
    kernel: String,
    uptime: u64,
    cpu_count: usize,
    cpu_usage: f32,
    total_memory: u64,
    used_memory: u64,
    total_swap: u64,
    used_swap: u64,
}

pub async fn run(section: Option<InfoSection>, format: &OutputFormat) -> Result<()> {
    let mut sys = System::new_all();
    sys.refresh_all();

    let section = section.unwrap_or(InfoSection::All);

    match section {
        InfoSection::Cpu => print_cpu_info(&sys, format),
        InfoSection::Memory => print_memory_info(&sys, format),
        InfoSection::Disk => print_disk_info(format),
        InfoSection::Network => print_network_info(format),
        InfoSection::All => {
            print_cpu_info(&sys, format);
            println!();
            print_memory_info(&sys, format);
            println!();
            print_disk_info(format);
            println!();
            print_network_info(format);
        }
    }

    Ok(())
}

fn print_cpu_info(sys: &System, format: &OutputFormat) {
    let cpu_count = sys.cpus().len();
    let cpu_usage: f32 = sys.cpus().iter().map(|c| c.cpu_usage()).sum::<f32>() / cpu_count as f32;

    match format {
        OutputFormat::Json => {
            let data = serde_json::json!({
                "cpu_count": cpu_count,
                "cpu_usage_percent": cpu_usage,
                "cpu_brand": sys.cpus().first().map(|c| c.brand()).unwrap_or("Unknown"),
            });
            println!("{}", serde_json::to_string_pretty(&data).unwrap());
        }
        _ => {
            println!("=== CPU 정보 ===");
            println!("코어 수: {}", cpu_count);
            println!("사용률: {:.1}%", cpu_usage);
            if let Some(cpu) = sys.cpus().first() {
                println!("모델: {}", cpu.brand());
            }
        }
    }
}

fn print_memory_info(sys: &System, format: &OutputFormat) {
    let total = sys.total_memory();
    let used = sys.used_memory();
    let total_swap = sys.total_swap();
    let used_swap = sys.used_swap();

    match format {
        OutputFormat::Json => {
            let data = serde_json::json!({
                "total_memory_mb": total / 1024 / 1024,
                "used_memory_mb": used / 1024 / 1024,
                "memory_usage_percent": (used as f64 / total as f64) * 100.0,
                "total_swap_mb": total_swap / 1024 / 1024,
                "used_swap_mb": used_swap / 1024 / 1024,
            });
            println!("{}", serde_json::to_string_pretty(&data).unwrap());
        }
        _ => {
            println!("=== 메모리 정보 ===");
            println!("총 메모리: {} MB", total / 1024 / 1024);
            println!("사용량: {} MB ({:.1}%)", used / 1024 / 1024, (used as f64 / total as f64) * 100.0);
            println!("총 스왑: {} MB", total_swap / 1024 / 1024);
            println!("사용 스왑: {} MB", used_swap / 1024 / 1024);
        }
    }
}

fn print_disk_info(format: &OutputFormat) {
    use sysinfo::Disks;

    let disks = Disks::new_with_refreshed_list();

    match format {
        OutputFormat::Json => {
            let data: Vec<_> = disks.iter().map(|d| {
                serde_json::json!({
                    "mount_point": d.mount_point().to_string_lossy(),
                    "filesystem": d.file_system().to_string_lossy(),
                    "total_gb": d.total_space() / 1024 / 1024 / 1024,
                    "available_gb": d.available_space() / 1024 / 1024 / 1024,
                })
            }).collect();
            println!("{}", serde_json::to_string_pretty(&data).unwrap());
        }
        _ => {
            println!("=== 디스크 정보 ===");
            println!("{:<20} {:<10} {:<10} {:<10}", "마운트 포인트", "파일 시스템", "총용량", "사용 가능");
            for disk in disks.iter() {
                println!("{:<20} {:<10} {:<8} GB  {:<6} GB",
                    disk.mount_point().to_string_lossy(),
                    disk.file_system().to_string_lossy(),
                    disk.total_space() / 1024 / 1024 / 1024,
                    disk.available_space() / 1024 / 1024 / 1024);
            }
        }
    }
}

fn print_network_info(format: &OutputFormat) {
    use sysinfo::Networks;

    let networks = Networks::new_with_refreshed_list();

    match format {
        OutputFormat::Json => {
            let data: Vec<_> = networks.iter().map(|(name, data)| {
                serde_json::json!({
                    "interface": name,
                    "received_mb": data.total_received() / 1024 / 1024,
                    "transmitted_mb": data.total_transmitted() / 1024 / 1024,
                })
            }).collect();
            println!("{}", serde_json::to_string_pretty(&data).unwrap());
        }
        _ => {
            println!("=== 네트워크 정보 ===");
            println!("{:<15} {:<15} {:<15}", "인터페이스", "수신", "전송");
            for (name, data) in networks.iter() {
                println!("{:<15} {:<10} MB  {:<10} MB",
                    name,
                    data.total_received() / 1024 / 1024,
                    data.total_transmitted() / 1024 / 1024);
            }
        }
    }
}

3.2 시스템 정보 모듈 테스트

BASH
# 컴파일
cargo build --release

# 모든 시스템 정보 보기
./target/release/sysmgr info

# CPU 정보만 보기
./target/release/sysmgr info --section cpu

# JSON 형식 출력
./target/release/sysmgr info --format json

# 메모리 정보 보기 (JSON)
./target/release/sysmgr info --section memory --format json

4. 프로세스 관리 모듈

4.1 프로세스 목록 구현

RUST
// src/commands/process.rs
use crate::cli::{ProcAction, OutputFormat};
use anyhow::Result;
use sysinfo::{System, Process, Pid};
use serde::Serialize;

#[derive(Serialize)]
struct ProcessInfo {
    pid: u32,
    name: String,
    cpu_usage: f32,
    memory_mb: u64,
    status: String,
    user: String,
}

pub async fn run(action: ProcAction, format: &OutputFormat) -> Result<()> {
    match action {
        ProcAction::List { name, sort_by_cpu } => {
            list_processes(name, sort_by_cpu, format).await?;
        }
        ProcAction::Kill { pid, force } => {
            kill_process(pid, force).await?;
        }
        ProcAction::Watch { name, interval } => {
            watch_process(name, interval).await?;
        }
    }
    Ok(())
}

async fn list_processes(
    name_filter: Option<String>,
    sort_by_cpu: bool,
    format: &OutputFormat,
) -> Result<()> {
    let mut sys = System::new_all();
    sys.refresh_all();

    let mut processes: Vec<ProcessInfo> = sys.processes().iter().map(|(pid, proc_)| {
        ProcessInfo {
            pid: pid.as_u32(),
            name: proc_.name().to_string_lossy().to_string(),
            cpu_usage: proc_.cpu_usage(),
            memory_mb: proc_.memory() / 1024 / 1024,
            status: format!("{:?}", proc_.status()),
            user: proc_.user_id().map(|u| u.to_string()).unwrap_or_default(),
        }
    }).collect();

    // 필터링
    if let Some(name) = name_filter {
        processes.retain(|p| p.name.to_lowercase().contains(&name.to_lowercase()));
    }

    // 정렬
    if sort_by_cpu {
        processes.sort_by(|a, b| b.cpu_usage.partial_cmp(&a.cpu_usage).unwrap());
    } else {
        processes.sort_by_key(|p| p.pid);
    }

    // 출력
    match format {
        OutputFormat::Json => {
            println!("{}", serde_json::to_string_pretty(&processes)?);
        }
        OutputFormat::Csv => {
            println!("PID,NAME,CPU%,MEMORY_MB,STATUS,USER");
            for p in &processes {
                println!("{},{},{:.1},{},{},{}", p.pid, p.name, p.cpu_usage, p.memory_mb, p.status, p.user);
            }
        }
        OutputFormat::Table => {
            println!("{:<8} {:<25} {:<8} {:<10} {:<10} {}", "PID", "이름", "CPU%", "메모리(MB)", "상태", "사용자");
            println!("{}", "-".repeat(80));
            for p in processes.iter().take(50) {
                println!("{:<8} {:<25} {:<8.1} {:<10} {:<10} {}",
                    p.pid, truncate(&p.name, 24), p.cpu_usage, p.memory_mb, p.status, p.user);
            }
            println!("\n총 {} 개 프로세스", processes.len());
        }
    }

    Ok(())
}

fn truncate(s: &str, max_len: usize) -> String {
    if s.len() > max_len {
        format!("{}...", &s[..max_len - 3])
    } else {
        s.to_string()
    }
}

async fn kill_process(pid: u32, force: bool) -> Result<()> {
    let mut sys = System::new();
    sys.refresh_processes(sysinfo::ProcessesToUpdate::All, true);

    let pid = Pid::from(pid as usize);

    if let Some(process) = sys.process(pid) {
        if force {
            process.kill();
            println!("프로세스 {}를 강제로 종료했습니다 ({})", pid, process.name().to_string_lossy());
        } else {
            // SIGTERM 전송
            #[cfg(unix)]
            {
                use std::process::Command;
                Command::new("kill").arg(pid.to_string()).output()?;
                println!("프로세스 {} ({})에 TERM 신호를 보냈습니다", pid, process.name().to_string_lossy());
            }
        }
    } else {
        println!("프로세스 {}를 찾을 수 없습니다", pid);
    }

    Ok(())
}

async fn watch_process(name: String, interval: u64) -> Result<()> {
    use std::time::Duration;

    println!("프로세스 {} 모니터링 중 (매 {} 초마다 갱신, Ctrl+C로 종료)", name, interval);

    loop {
        let mut sys = System::new();
        sys.refresh_processes(sysinfo::ProcessesToUpdate::All, true);

        let matched: Vec<_> = sys.processes().iter()
            .filter(|(_, p)| p.name().to_string_lossy().to_lowercase().contains(&name.to_lowercase()))
            .collect();

        // 화면 지우기
        print!("\x1B[2J\x1B[1;1H");
        println!("=== 프로세스 모니터: {} ===", name);
        println!("시간: {}", chrono::Local::now().format("%Y-%m-%d %H:%M:%S"));
        println!();

        if matched.is_empty() {
            println!("'{}'과 일치하는 프로세스를 찾을 수 없습니다", name);
        } else {
            println!("{:<8} {:<25} {:<8} {:<10}", "PID", "이름", "CPU%", "메모리(MB)");
            println!("{}", "-".repeat(55));
            for (pid, proc_) in &matched {
                println!("{:<8} {:<25} {:<8.1} {:<10}",
                    pid, proc_.name().to_string_lossy(),
                    proc_.cpu_usage(),
                    proc_.memory() / 1024 / 1024);
            }
        }

        tokio::time::sleep(Duration::from_secs(interval)).await;
    }
}

4.2 프로세스 관리 테스트

BASH
# 모든 프로세스 목록 보기
./target/release/sysmgr proc list

# CPU 사용률로 정렬
./target/release/sysmgr proc list --sort-by-cpu

# 특정 프로세스 필터링
./target/release/sysmgr proc list --name nginx

# JSON 형식 출력
./target/release/sysmgr proc list --format json

# 프로세스 종료
./target/release/sysmgr proc kill 1234

# 강제 종료
./target/release/sysmgr proc kill 1234 --force

# 프로세스 모니터링
./target/release/sysmgr proc watch nginx --interval 5

5. 로그 분석 모듈

5.1 로그 분석 구현

RUST
// src/commands/log.rs
use crate::cli::OutputFormat;
use anyhow::Result;
use regex::Regex;
use serde::Serialize;
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader};

#[derive(Serialize)]
struct LogStats {
    total_lines: usize,
    error_count: usize,
    warn_count: usize,
    info_count: usize,
    debug_count: usize,
    errors_per_hour: HashMap<String, usize>,
}

pub async fn run(
    file: &str,
    keyword: Option<&str>,
    stats: bool,
    format: &OutputFormat,
) -> Result<()> {
    let file = File::open(file)?;
    let reader = BufReader::new(file);

    if stats {
        analyze_stats(reader, format)?;
    } else if let Some(kw) = keyword {
        filter_lines(reader, kw, format)?;
    } else {
        println!("--keyword 또는 --stats 인수를 지정해주세요");
    }

    Ok(())
}

fn analyze_stats(reader: BufReader<File>, format: &OutputFormat) -> Result<()> {
    let mut stats = LogStats {
        total_lines: 0,
        error_count: 0,
        warn_count: 0,
        info_count: 0,
        debug_count: 0,
        errors_per_hour: HashMap::new(),
    };

    // 정규표현식: 타임스탬프와 로그 레벨 매칭
    let re = Regex::new(r"(?P<timestamp>\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}).*(?P<level>ERROR|WARN|INFO|DEBUG)")?;

    for line in reader.lines() {
        let line = line?;
        stats.total_lines += 1;

        if let Some(caps) = re.captures(&line) {
            let level = caps.name("level").unwrap().as_str();
            let timestamp = caps.name("timestamp").unwrap().as_str();

            match level {
                "ERROR" => {
                    stats.error_count += 1;
                    // 시간 추출
                    if let Some(hour) = timestamp.get(11..13) {
                        *stats.errors_per_hour.entry(hour.to_string()).or_insert(0) += 1;
                    }
                }
                "WARN" => stats.warn_count += 1,
                "INFO" => stats.info_count += 1,
                "DEBUG" => stats.debug_count += 1,
                _ => {}
            }
        }
    }

    match format {
        OutputFormat::Json => {
            println!("{}", serde_json::to_string_pretty(&stats)?);
        }
        _ => {
            println!("=== 로그 통계 ===");
            println!("총 라인 수: {}", stats.total_lines);
            println!("ERROR: {}", stats.error_count);
            println!("WARN: {}", stats.warn_count);
            println!("INFO: {}", stats.info_count);
            println!("DEBUG: {}", stats.debug_count);

            if !stats.errors_per_hour.is_empty() {
                println!("\n=== 시간별 오류 분포 ===");
                let mut hours: Vec<_> = stats.errors_per_hour.iter().collect();
                hours.sort_by_key(|(h, _)| h.clone());
                for (hour, count) in hours {
                    println!("{}:00 - {} 오류", hour, count);
                }
            }
        }
    }

    Ok(())
}

fn filter_lines(reader: BufReader<File>, keyword: &str, format: &OutputFormat) -> Result<()> {
    let re = Regex::new(&format!("(?i){}", regex::escape(keyword)))?;
    let mut count = 0;

    for line in reader.lines() {
        let line = line?;
        if re.is_match(&line) {
            count += 1;
            match format {
                OutputFormat::Json => {
                    println!("{}", serde_json::json!({"line": count, "content": line}));
                }
                _ => {
                    println!("{}", line);
                }
            }
        }
    }

    eprintln!("\n{}개의 일치하는 라인을 찾았습니다", count);
    Ok(())
}

5.2 로그 분석 테스트

BASH
# 로그 통계
./target/release/sysmgr log --file /var/log/syslog --stats

# JSON 형식 통계
./target/release/sysmgr log --file /var/log/syslog --stats --format json

# 키워드 필터링
./target/release/sysmgr log --file /var/log/syslog --keyword "error"

# 필터링 및 JSON 출력
./target/release/sysmgr log --file /var/log/syslog --keyword "failed" --format json

6. 자동화 작업 모듈

6.1 Cron 관리 구현

RUST
// src/commands/cron.rs
use crate::cli::{CronAction, OutputFormat};
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;

#[derive(Serialize, Deserialize, Debug)]
struct CronJob {
    id: u32,
    schedule: String,
    command: String,
    enabled: bool,
}

#[derive(Serialize, Deserialize, Debug, Default)]
struct CronConfig {
    jobs: Vec<CronJob>,
}

fn config_path() -> PathBuf {
    let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
    PathBuf::from(home).join(".sysmgr").join("cron.json")
}

fn load_config() -> Result<CronConfig> {
    let path = config_path();
    if !path.exists() {
        return Ok(CronConfig::default());
    }
    let content = fs::read_to_string(path)?;
    Ok(serde_json::from_str(&content)?)
}

fn save_config(config: &CronConfig) -> Result<()> {
    let path = config_path();
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)?;
    }
    fs::write(path, serde_json::to_string_pretty(config)?)?;
    Ok(())
}

pub async fn run(action: CronAction, format: &OutputFormat) -> Result<()> {
    match action {
        CronAction::List => {
            let config = load_config()?;
            match format {
                OutputFormat::Json => {
                    println!("{}", serde_json::to_string_pretty(&config.jobs)?);
                }
                _ => {
                    println!("{:<5} {:<20} {:<40} {:<8}", "ID", "스케줄", "명령", "상태");
                    println!("{}", "-".repeat(75));
                    for job in &config.jobs {
                        let status = if job.enabled { "활성화" } else { "비활성화" };
                        println!("{:<5} {:<20} {:<40} {:<8}", job.id, job.schedule, job.command, status);
                    }
                }
            }
        }
        CronAction::Add { schedule, command } => {
            let mut config = load_config()?;
            let new_id = config.jobs.iter().map(|j| j.id).max().unwrap_or(0) + 1;
            config.jobs.push(CronJob {
                id: new_id,
                schedule,
                command,
                enabled: true,
            });
            save_config(&config)?;
            println!("작업 ID {} 추가됨", new_id);
        }
        CronAction::Remove { id } => {
            let mut config = load_config()?;
            let initial_len = config.jobs.len();
            config.jobs.retain(|j| j.id != id);
            if config.jobs.len() == initial_len {
                println!("작업 ID를 찾을 수 없습니다: {}", id);
            } else {
                save_config(&config)?;
                println!("작업 ID {} 제거됨", id);
            }
        }
    }
    Ok(())
}

6.2 자동화 작업 테스트

BASH
# 예약 작업 추가
./target/release/sysmgr cron add "0 */6 * * *" "/usr/bin/backup.sh"

# 모든 작업 목록 보기
./target/release/sysmgr cron list

# JSON 형식 목록
./target/release/sysmgr cron list --format json

# 작업 제거
./target/release/sysmgr cron remove 1

7. 고급 기능

7.1 크로스플랫폼 컴파일

BASH
# Linux x86_64용 컴파일
cargo build --release --target x86_64-unknown-linux-gnu

# Linux ARM64용 컴파일
cargo build --release --target aarch64-unknown-linux-gnu

# macOS용 컴파일
cargo build --release --target x86_64-apple-darwin

# Windows용 컴파일
cargo build --release --target x86_64-pc-windows-gnu

7.2 정적 링크 (glibc 버전 문제 회피)

BASH
# musl을 사용한 정적 링크
rustup target add x86_64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-musl

# 생성된 바이너리는 모든 Linux 배포판에서 실행 가능
ls -lh target/x86_64-unknown-linux-musl/release/sysmgr

7.3 패키징 및 배포

BASH
# cargo-deb로 Debian 패키지 생성
cargo install cargo-deb
cargo deb

# cargo-generate-rpm으로 RPM 패키지 생성
cargo install cargo-generate-rpm
cargo generate-rpm

# tar.gz 배포 패키지 생성
tar -czvf sysmgr-0.1.0-linux-x86_64.tar.gz \
    -C target/release sysmgr

8. 성능 최적화

8.1 컴파일 최적화

TOML
# Cargo.toml
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
panic = "abort"
strip = true

8.2 런타임 최적화

  • mimalloc 대신 기본 할당자 사용
RUST
// src/main.rs
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
  • 대량 로그 처리를 위한 비동기 I/O
RUST
use tokio::io::AsyncBufReadExt;

async fn process_large_log(path: &str) -> Result<()> {
    let file = tokio::fs::File::open(path).await?;
    let reader = tokio::io::BufReader::new(file);
    let mut lines = reader.lines();

    while let Some(line) = lines.next_line().await? {
        // 각 라인 처리
    }
    Ok(())
}

9. 요약

이 글에서는 Rust를 사용하여 다음과 같은 기능을 갖춘 완전한 Linux 서버 관리 CLI 도구를 구축하는 방법을 안내했습니다:

  1. CLI 프레임워크 구축: clap을 사용해 명확한 명령 구조 정의
  2. 시스템 정보 수집: CPU, 메모리, 디스크, 네트워크의 실시간 모니터링
  3. 프로세스 관리: 목록 보기, 필터링, 종료, 모니터링
  4. 로그 분석: 필터링, 통계, 오류 분포 분석
  5. 자동화 작업: Cron 작업의 CRUD 관리
  6. 크로스플랫폼 컴파일: 단일 소스 코드로 멀티플랫폼 바이너리 컴파일
  7. 성능 최적화: 정적 링크, LTO, mimalloc 등

Rust의 장점이 서버 관리 도구 개발에서 충분히 발휘됨을 보여줍니다:

  • 안전성: 컴파일 타임에 데이터 경합과 메모리 문제 방지
  • 성능: C/C++에 상응하는 실행 효율성
  • 이식성: 크로스컴플레이션으로 다양한 아키텍처에 손쉬운 배포
  • 생태계: 풍부한 크레이트로 개발 속도 향상

관련 자료: - sysinfo 문서 - clap 문서 - tokio 문서 - Rust CLI 애플리케이션 가이드