[작성자:] d_dive

  • Linux 환경에서 PM2 사용자 계정 설정하기

    Linux 환경에서 PM2 사용자 계정 설정하기

    Linux 환경에서 PM2 사용자 계정 설정하기

    yum update # 먼저 하지말자 나중에
    # wget 설치 확인.
    yum install wget
    
    # nvm 다운로드 및 설치.
    wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.2/install.sh | bash
    
    # 설치가 완료되면 .bashrc 파일에 아래 설정이 추가 되어짐.
    # .bashrc export NVM_DIR="$HOME/.nvm"
    
    [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm 
    [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" 
    
    # This loads nvm bash_completion
    # nvm 설정 적용.
    source ~/.bashrc
    
    # nvm 설치 버전 확인.
    nvm - version
    # 특정 버전 설치.
    nvm use 16.17.0
    # npm, nodejs 설치 확인.
    # 정상적으로 설치 되어졌는지 확인.
    node -v 
    npm -v

    PM2 설치

    npm install pm2 -g
    
    # -g : global 설치

    PM2-logrotate 설치

    pm2 install pm2-logrotate

    마지막으로, 설치가 잘되어졌는지 확인

  • scp 명령어로 Mac에서 Linux로 파일 이동하기

    scp 명령어로 Mac에서 Linux로 파일 이동하기

    scp 명령어로 Mac에서 Linux로 파일 이동하기

    scp [option] [user@]SRC_HOST:]file1 [user@]DEST_HOST:]file2
    
    scp -r aa root@localhost:~/test # 폴더 옮기기 -r

  • CentOS 7에서 Anaconda 설치 가이드

    CentOS 7에서 Anaconda 설치 가이드

    CentOS 7에서 Anaconda 설치 가이드

    $ wget https://repo.anaconda.com/archive/Anaconda3-2019.10-Linux-x86_64.sh
    bash Anaconda3–2019.10-Linux-x86_64.sh
    source ~/.bashrc
    conda — version #아나콘다 버전확인
    conda update conda #아나콘다 업데이트
    conda info — envs #가상환경 리스트 확인 conda list #현재 activate 환경에 설치된 패키지 조회 
    conda remove -n 환경이름 패키지이름 #해당 경에 패키지 삭제
    conda search -n 환경이름 패키지이름 #해당 환경에 패키지 찾기
  • CentOS 7 한글 폰트 및 로케일 설정 가이드

    CentOS 7 한글 폰트 및 로케일 설정 가이드

    CentOS 7 Minimal 버전에서는 기본적으로 한글 폰트가 포함되어 있지 않습니다. 따라서 `fonts-nanum*` 패키지를 설치해야 합니다.

    sudo yum install -y fonts-nanum

    설치 가능한 폰트 찾기.

    로케일(Locale) 설정: 로케일을 설정하여 시스템 언어를 한글로 변경합니다.

    sudo localectl set-locale LANG=ko_KR.UTF-8

    `/etc/locale.conf` 파일을 수정하여 변경 내용을 영구적으로 설정합니다.

    sudo echo ‘LANG=ko_KR.UTF-8’ > /etc/locale.conf
  • RedHat Linux에서 Python 3.8 버전 설치 방법

    RedHat Linux에서 Python 3.8 버전 설치 방법

    • gcc와 Development Tools 패키지를 설치
    sudo yum groupinstall -y "Development Tools"
    sudo yum install -y gcc
    sudo yum install -y openssl-devel bzip2-devel libffi-devel
    • python3.8 소스 파일을 다운로드
    cd /usr/src
    sudo wget https://www.python.org/ftp/python/3.8.0/Python-3.8.0.tgz
    sudo tar xzf Python-3.8.0.tgz
    • python3.8을 빌드 -> 설치
    cd Python-3.8.0
    sudo ./configure --enable-optimizations
    sudo make altinstall
    • python3.8 버전 확인
    python3.8 --version
  • Load balancer Timeout 오류

    Load balancer Timeout 오류

    AWS Load balancer로 구축된 웹 서버와 API 서버가 있다.

    • A load balancer — Nginx 웹 서버 (instance 3대)
    • B load balancer — API 서버 (instance 3대)

    이렇게 각각 2개의 로드밸런서를 구축하고 아무런 문제 없이 운영하고 있는데 오류가 발생했다.

    오류의 내용은, timeout error 였다.

    2024/11/05 14:40:24 [error] 905#0: *974 upstream timed out (110: Connection timed out) while reading response header from upstream

    이런 내용의 에러 였다.

    구글링을 해보니, 프록시 서버에서의 읽는 시간과 연결 시간이 너무 짧게 설정되어 있어서 해당 오류가 뜬다고 나와있다.

    nginx.conf 파일을 살펴보니 따로 timeout에 대해 설정이 되어 있진 않았다. 설정이 되어있지 않다면 디폴트값은 다음과 같다.

    proxy_connect_timeout: 60초
    proxy_read_timeout: 60초
    proxy_send_timeout: 60초

    일단 로드밸런서의 서버 리소스는 충분했고, DB 지연시간이나 DB CPU가 많이 먹는 상태는 아니었기 때문에 1순위로 timeout 설정이 잘못되어 졌다고 생각했다.

    오류가 난 서버에 위 설정을 두배정도인 120s 로 설정 했다.

    proxy_connect_timeout 120s;
    proxy_read_timeout 120s;
    proxy_send_timeout 120s;

    그리고, 한가지 더 의심이 갔던 부분은 로드밸런서를 구성할 때 대상그룹의 상태검사 부분에서 health check 하는 부분이 있다.

    여기서도 timeout — 제한시간이라는 이름으로 응답에 관련한 항목이 있다.

    이 부분도 web 서버의 로드밸런서와 api 서버의 로드밸런서가 서로 시간이 상이했기 때문에 upstream timeout 에러가 났다고 추측했다.

    제한시간 타임을 조금 늘리고, 각각의 로드밸런서의 시간을 동일하게 맞췄다.

    그리고 나서는 따로 오류가 나지 않는 상태.

  • Locky Linux 8 (CentOS7) | WEB 서버 Setting

    Locky Linux 8 (CentOS7) | WEB 서버 Setting

    관련 패키지 및 버전

    nginx — 1.26.2, node.js — 16.20.2, npm — 8.19.4, pm2–5.4.2

    selinux 설정 끄기

    sudo getenforce # enforce 상태 확인.
    sudo setenforce 0 # enforce 상태 변경. (끄기)

    📌 selinux 영구적으로 끄는 법

    # 파일 수정.
    sudo vi /etc/selinux/config
    # 이 항목을 찾는다.
    SELINUX=enforcing
    # 다음과 같이 변경.
    SELINUX=disabled
    # 시스템 재부팅.
    sudo reboot
    # 적용되었는지 확인.
    sestatus

    커널 sysctl 수정 (소켓 maxconn 셋팅값 수정.) -> maxconn 값은 상황에 따라 수정 가능

    # maxconn 상태 조회.
    sudo sysctl -a | grep net.core.somaxconn
    # maxconn 상태 수정.
    sudo /sbin/sysctl -w net.core.somaxconn=4096
    
    ---
    net.core.somaxconn = 65535

    port 체크

    sudo netstat -nltup

    시스템 업데이트 및 필수 패키지 설치

    sudo dnf update -y
    sudo dnf install -y curl wget tar gcc-c++ make

    Nginx 셋팅 (version : 1.26.2)

    # 필요한 패키지 설치
    sudo dnf install -y gcc pcre-devel zlib-devel make openssl-devel
    # Nginx 소스 다운로드
    wget https://nginx.org/download/nginx-1.26.2.tar.gz
    # 압축 해제 및 설치
    tar -zxvf nginx-1.26.2.tar.gz
    cd nginx-1.26.2
    # Nginx 컴파일 및 설치
    ./configure
    make
    sudo make install
    # Nginx 실행 확인
    /usr/local/nginx/sbin/nginx -v

    Nginx 환경변수 설정

    vi ~/.bashrc # 해당 파일에 아래와 같은 내용 추가.
    export PATH=$PATH:/usr/local/nginx/sbin
    source ~/.bashrc # 적용.
    nginx -v # 확인.

    Nginx 서비스 설정

    root ~ # vi /etc/systemd/system/nginx.service
    
    [Unit]
    Description=The NGINX HTTP and reverse proxy server
    After=network.target
    
    [Service]
    Type=forking
    PIDFile=/usr/local/nginx/logs/nginx.pid
    ExecStartPre=/usr/local/nginx/sbin/nginx -t
    ExecStart=/usr/local/nginx/sbin/nginx
    ExecReload=/usr/local/nginx/sbin/nginx -s reload
    ExecStop=/usr/local/nginx/sbin/nginx -s stop
    PrivateTmp=true
    
    [Install]
    WantedBy=multi-user.target
    
    ---
    
    systemctl daemon-reload
    systemctl status nginx
    systemctl start nginx
    systemctl enable nginx

    Node.js 16.20.2 설치

    # nvm 설치
    curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash
    source ~/.bashrc
    # nvm으로 Node.js 16.20.2 설치
    nvm install 16.20.2
    nvm use 16.20.2
    # 설치된 Node.js 버전 확인
    node -v
    npm -v

    NPM 8.19.4 설치

    #### 위에서 버전이 나오면 따로 설치 안해도 됨.
    npm install -g npm@8.19.4
    # 설치된 NPM 버전 확인
    npm -v

    PM2 5.4.2 설치

    npm install -g pm2@5.4.2
    # PM2 버전 확인
    pm2 -v

    PM2 사용법

    # 애플리케이션 시작
    pm2 start app.js
    # 모든 애플리케이션 목록 확인
    pm2 list
    # 애플리케이션 로그 확인
    pm2 logs
    # 애플리케이션 재시작
    pm2 restart app
    # PM2 상태 저장 (서버 재부팅 시 자동 시작)
    pm2 save
    pm2 startup

  • Ollama 외부 접속

    Ollama 외부 접속

    ollama를 외부접속하게 만들려고 한다. 그래서 ollama 설치 후, systemd 등록 파일로 프로세스를 띄웠다.

    [Unit]
    Description=Ollama Service
    After=network.target
    
    [Service]
    ExecStart=/usr/local/bin/ollama serve
    User=root
    Restart=always
    RestartSec=3
    
    Environment="OLLAMA_HOST=0.0.0.0"
    
    [Install]
    WantedBy=multi-user.target

    default 가 127.0.0.1:11434로 프로세스가 띄워지기 때문에 외부에서 접속할 수 있게 0.0.0.0:11434으로 띄워야 한다. Environment="OLLAMA_HOST=0.0.0.0" 으로 등록하여 anywhere로 접근하게 만들었다.

    하지만, TCP6로만 열리고, TCP4로는 열리지 않았다.

    이런 문제를 해결하기 위해 nginx로 로드밸런서 처리를 하여 외부에서 ollama를 접속할 수 있게 만들었다.

    # /etc/nginx/site-availables/
    
    server {
        listen 11433; # ollama port
    
        location / {
            proxy_pass http://[::1]:11434;  # Ollama 서버의 IPv6 주소와 포트
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
        }
    }

    nginx에서 이렇게 설정하여, nginx에 들어오는 트래픽을 ollama로 들어가게 설정하였다.

    새로운 설정 파일 활성화

    sites-enabled 디렉터리에 올바른 파일이 없기 때문에, 이전에 생성한 ollama 설정 파일을 활성화.

    sudo ln -s /etc/nginx/sites-available/ollama /etc/nginx/sites-enabled/default

    Nginx 설정 다시 확인

    올바른 파일이 활성화되었는지 확인.

    sudo nginx -t

  • Cross-Origin Resource Sharing(CORS) 에러

    Cross-Origin Resource Sharing(CORS) 에러

    Cross-Origin Resource Sharing(CORS) 에러

    🚧 CORS 란.

    CORS (코어스) 는 Cross-Origin-Resource Sharing의 줄임말. “서로 다른 사이트끼리 데이터를 주고 받는 걸 제어하는 웹 보안 규칙”

    쉽게 말해서, “다른 사이트에서는 함부로 내 데이터 보지마!” 라고 통제한다.

    📣 예시

    나는 지금 https://www.food-order.com 라는 사이트(프론트), 주문 정보를 https://api.food-system.com 라는 다른 주소(API 서버)에 요청.

    이렇게 주소가 다르면 “다른 출처(Origin)”이라고 말한다.

    그래서 서버는 반드시 브라우저에게 이렇게 허락을 해줘야 한다.

    Access-Control-Allow-Origin: https://www.food-order.com

    이걸 “CORS 응답 헤더” 라고 부른다.

    ⛔️ CORS 에러는 왜 생기는 걸까?

    서버가 허락을 안해주면, 브라우저는 이렇게 차단한다.

    ❌ “이 서버는 너한테 응답 못해. 보안 때문에 막을거야”

    ✅ 해결방법

    서버에게 정확한 도메인을 허용해주거나, 특정 IP로 허용해주는 설정을 해주면 된다.

    ## 여러개의 IP 등록 할때..
    
    if ($remote_addr = "1.1.1.1") {
        set $cors_origin $http_origin;
    }
    
    if ($remote_addr = "2.2.2.2") {
        set $cors_origin $http_origin;
    }
    
    if ($http_origin = "허락할도메인") {
        set $cors_origin $http_origin;
    }
    location / {
                uwsgi_pass ;
    
                # 서버가 uwsgi를 쓴다면 필요할 수도..
                uwsgi_hide_header Access-Control-Allow-Origin; 
                uwsgi_hide_header Access-Control-Allow-Methods;
                uwsgi_hide_header Access-Control-Allow-Headers;
                uwsgi_hide_header Access-Control-Allow-Credentials;
    
                set $cors_origin "";
    
                if ($remote_addr = "허락할 특정IP") { # 필요없으면 이 블럭 빼면 됨.
                    set $cors_origin $http_origin;
                }
    
                if ($http_origin = "허락할 domain") { # 필요없으면 이 블럭 빼면 됨.
                    set $cors_origin $http_origin;
                }
    
                
                add_header 'Access-Control-Allow-Origin' "$cors_origin" always;
                add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
                add_header 'Access-Control-Allow-Headers' 'Origin, Content-Type, Accept, Authorization' always;
                add_header 'Access-Control-Allow-Credentials' 'true' always;
    
                if ($request_method = OPTIONS ) {
                    add_header 'Access-Control-Allow-Origin' "$cors_origin" always;
                    add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
                    add_header 'Access-Control-Allow-Headers' 'Origin, Content-Type, Accept, Authorization' always;
                    add_header 'Access-Control-Allow-Credentials' 'true' always;
                    add_header 'Access-Control-Max-Age' 3600;
                    add_header 'Content-Length' 0;
                    add_header 'Content-Type' text/plain;
                    return 204;
                }
            }
    
            location /route {
                allow ip; # 특정 IP
                deny all; # deny 정책
            }

    ⛺️ 옵션 설명

    🚀 Access-Control-Allow-Origin

    어떤 웹사이트에서 API를 요청할 수 있는지 지정, 이게 없으면 브라우저 차단 당한다.

    🚀 Access-Control-Allow-Methods

    어떤 HTTP 요청 종류를 허용할지 지정

    🚀 Access-Control-Allow-Headers

    브라우저가 보낼 수 있는 요청 헤더를 허용한다. 예를 들어, 클라이언트가 JWT 토큰을 담기 위해 Auth 헤더를 쓸 때, 서버가 이걸 허용해주지 않으면 요청 자체가 막힌다.

    Content-Type도 없으면 Application/json 같은 것도 막힌다.

    🚀 Access-Control-Allow-Credentials

    “쿠키, 인증정보도 같이 보내도 돼”를 허용

    브라우저에서 withCredentials: true를 사용하는 요청이 있으면 이 설정이 필수다.

    예: 로그인 상태 유지용 쿠키, 토큰 등을 브라우저가 같이 보내려면 이게 있어야 한다.

    🚀 Access-Control-Max-Age

    브라우저가 CORS 체크 결과를 얼마나 오래 기억할지 설정(초 단위)

    브라우저는 1시간 동안은 또 다시 OPTIONS 요청을 보내지 않고, 캐시된 정보를 쓴다.

    🔥 서버 부하를 줄이기 좋음

    🚀 Content-Length 와 Content-Type

    OPTIONS 요청의 응답 내용 설정

    • OPTIONS는 데이터를 주고받지 않기 때문에, 응답 길이는 0
    • 그냥 텍스트로 응답하겠다는 의미
    • 브라우저가 이 응답을 제대로 처리하기 위해 필요함
  • Loki, Promtail 이용한 실시간 로그 모니터링 + Docker

    Loki, Promtail 이용한 실시간 로그 모니터링 + Docker

    구성

    Promtail

    • loki프로젝트에서 제공하는 로그 수집기

    ✅ Promtail 바이너리 다운로드

    # 버전 확인 (예: 2.9.4)
    VERSION="2.9.4"
    
    # 바이너리 다운로드 및 압축 해제
    wget https://github.com/grafana/loki/releases/download/v${VERSION}/promtail-linux-amd64.zip
    unzip promtail-linux-amd64.zip
    chmod +x promtail-linux-amd64
    sudo mv promtail-linux-amd64 /usr/local/bin/promtail

    ✅ Config 파일 작성

    server:
      http_listen_port: 9080
      grpc_listen_port: 0
    
    positions:
      filename: /var/log/positions.yaml
    
    clients:
      - url: http://localhost:3100/loki/api/v1/push  # Loki 주소로 입력해야 함.
    
    scrape_configs:
      - job_name: system
        static_configs:
          - targets:
              - localhost
            labels:
              job: varlogs # QL 에서 잡을 job 이름.
              __path__: /var/log/*.log # 수집할 로그 파일

    ✅ Systemd 서비스 등록

    ## /etc/systemd/system directory
    
    [Unit]
    Description=Promtail Log Collector
    After=network.target
    
    [Service]
    Type=simple
    ExecStart=/usr/local/bin/promtail -config.file=/etc/promtail/config.yaml
    Restart=always
    
    [Install]
    WantedBy=multi-user.target
    
    ---
    systemctl daemon-reload 

    🐳 Docker로 Promtail Container

    ✅ Promtail config 파일 작성

    # config-promtail.yaml
    
    server:
      http_listen_port: 9080
      grpc_listen_port: 0
    
    positions:
      filename: /tmp/positions.yaml
    
    clients:
      - url: http://loki:3100/loki/api/v1/push  # Loki의 주소에 맞게 수정
    
    scrape_configs:
      - job_name: system
        static_configs:
          - targets:
              - localhost
            labels:
              job: varlogs
              __path__: /var/log/*.log

    🐳 Docker로 Run

    docker run -dit \
      --name promtail \
      -v /var/log:/var/log \ # volume mount
      -v /$(pwd)/config-promtail.yaml:/etc/promtail/config.yaml \
      grafana/promtail:latest \
      -config.file=/etc/promtail/config.yaml

    🐙 Docker-Compose Version

    version : '3'
    
    services:
      promtail:
        image: grafana/promtail:latest
        volumes:
          - /var/log:/var/log
          - ./config-promtail.yaml:/etc/promtail/config.yaml
         command: -config.file=/etc/promtail/config.yaml
    
    ---
    docker-compose up -d promtail

    네트워크 구성

    🔥 Promtail과 loki 사이 통신 방식은 push. Promtail 에서 loki 쪽으로 데이터를 push 해줌


    Loki

    내가 사용하는 방식의 Loki는 Grafana, Prometheus 등 과 같이 올렸어야 했기 때문에 Docker Container 형식으로 올렸다. 따라서, Docker Container 으로 정리했다.

    🐙 Docker-Compose Version

    • loki-config.yaml
    auth_enabled: false
    
    server:
      http_listen_port: 3100
      log_level: info
    
    ingester:
      lifecycler:
        ring:
          kvstore:
            store: inmemory
          replication_factor: 1
      chunk_idle_period: 3m
      max_chunk_age: 1h
    
    schema_config:
      configs:
        - from: 2022-01-01
          store: boltdb-shipper
          object_store: filesystem
          schema: v11
          index:
            prefix: index_
            period: 24h
    
    storage_config:
      boltdb_shipper:
        active_index_directory: /tmp/loki/index
        cache_location: /tmp/loki/boltdb-cache
      filesystem:
        directory: /tmp/loki/chunks
    
    limits_config:
      allow_structured_metadata: false  # 이 항목이 없으면 최신 Loki에서 기동 실패
    
    common:
      path_prefix: /tmp/loki  # compactor 등 여러 모듈이 이 경로를 사용
    
    compactor:
      working_directory: /tmp/loki/compactor  # 필수
    
    ruler:
      alertmanager_url: http://alertmanager:9093
      enable_alertmanager_v2: true
      enable_api: true
      rule_path: /tmp/loki/rules-temp
      storage:
        type: local
        local:
          directory: /tmp/loki/rules
    loki:
      image: grafana/loki:latest # docker 제공 공식 image
      container_name: loki
      ports:
        - "3100:3100"
      command: -config.file=/etc/loki/config.yaml
      volumes:
        - /home/rocky/grafana/loki/loki-cfg.yaml:/etc/loki/config.yaml
        - /home/rocky/grafana/loki/loki-data/rules:/tmp/loki/rules
        - /home/rocky/grafana/loki/loki-data/rules-temp:/tmp/loki/rules-temp
        - /home/rocky/grafana/loki/loki-data/index:/tmp/loki/index
        - /home/rocky/grafana/loki/loki-data/chunks:/tmp/loki/chunks
        - /home/rocky/grafana/loki/loki-data/boltdb-cache:/tmp/loki/boltdb-cache
        - /home/rocky/grafana/loki/loki-data/compactor:/tmp/loki/compactor
        - /home/rocky/grafana/loki/loki-data/wal:/tmp/loki/wal
      restart: always
    
    
      networks: # 네트워크는 기존 docker-compose에서 Grafana와 같은 네트워크로 설정해주면 된다.(난 따로 설정 안함.)
        - grafana_default
    - /home/rocky/grafana/loki/loki-cfg.yaml:/etc/loki/config.yaml
        - /home/rocky/grafana/loki/loki-data/rules:/tmp/loki/rules
        - /home/rocky/grafana/loki/loki-data/rules-temp:/tmp/loki/rules-temp
        - /home/rocky/grafana/loki/loki-data/index:/tmp/loki/index
        - /home/rocky/grafana/loki/loki-data/chunks:/tmp/loki/chunks
        - /home/rocky/grafana/loki/loki-data/boltdb-cache:/tmp/loki/boltdb-cache
        - /home/rocky/grafana/loki/loki-data/compactor:/tmp/loki/compactor
        - /home/rocky/grafana/loki/loki-data/wal:/tmp/loki/wal

    위의 Volume mount한 디렉토리를 생성하지 않으면 Container가 실행되지 않는다.

    mkdir -p /home/rocky/grafana/loki/loki-data
    mkdir -p /home/rocky/grafana/loki/loki-data/{rules,rules-temp,index,chunks,boltdb-cache,compactor,wal}

    이렇게 먼저 디렉토리 먼저 생성

    퍼미션 부여

    chown -R 10001:10001 /home/rocky/grafana/loki/loki-data
    chmod -R 777 /home/rocky/grafana/loki/loki-data

    위에 처럼 설정을 다 완료하고, 컨테이너가 다 문제 없이 돌고 있다면, Grafana 에서 loki Data source를 등록해주면 된다.

    Grafana > ⚙️ Settings > Data Sources > loki

    URL이 다음 중 하나여야 한다.

    • http://loki:3100 ← Grafana와 Loki가 같은 도커 네트워크일 때 (보통 Compose 환경)
    • http://localhost:3100 ← Grafana가 호스트에 설치되어 있고 Loki도 호스트에서 도는 경우

    이렇게 설정해주면 쿼리를 날려 데이터가 잘 나오는지 확인하면 된다.