ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 노노그램 서버 제작기 3: 노트북에 서버 구축하기
    Programming/Projects 2024. 9. 14. 18:44

    도커로 빌드한 서버를 실제 배포하고 운영하는 과정을 다룹니다.
    원격 도커 배포 설정부터 DuckDNS를 활용한 도메인 연결, SWAG를 이용한 Nginx 리버스 프록시 구성과 SSL 인증서 설정등의 내용을 포함하고 있습니다.

    Intro

    이번에는 집에 남는 노트북에 서버를 올려 구동까지 해보겠습니다.

    이전에 쓰던 노트북이 힌지 부분이 완전히 박살나서 잘못 열면 모니터도 같이 와사삭 부셔지는 그런 상태인데요.
    나름 성능 자체는 괜찮아서 이참에 이 친구를 저전력 모드로 돌리면서 서버 컴퓨터로 쓰려고 했습니다.

    OS는 원래 깔려 있던 윈도우를 밀고 우분투를 깔아 주었고요.
    Tailscale을 통해 어디서든 원격 연결 할 수 있도록 설정해주었습니다.

    원격 연결에 대해서는 이전에 쓴 글 [[SSH로 원격 서버 안전하게 접속 관리하기]] 를 참조해 주시고요.
    그 외 자동 완성을 위한 zsh와 도커 데스크탑 정도를 설치 해 주었습니다.

    원격 배포하기

    services:
      app:
        build: .
        ports:
          - 8080:8080
        depends_on:
          db:
            condition: service_healthy
        environment:
          - DB_HOST=db 
          - DB_USER=pz_admin
          - DB_PASSWORD=${DB_PASSWORD}
          - DB_NAME=puzzle_db
          - DB_PORT=5432
      db:
        image: postgres:14
        restart: always
        healthcheck:
          test: ["CMD", "pg_isready"]
          interval: 10s
          timeout: 5s
          retries: 5
        volumes:
          - ./puzzle_db_dump.sql:/docker-entrypoint-initdb.d/puzzle_db_dump.sql
          - pgdata:/var/lib/postgresql/data
        environment:
          - POSTGRES_USER=pz_admin
          - POSTGRES_PASSWORD=${DB_PASSWORD}
          - POSTGRES_DB=puzzle_db
          - POSTGRES_PORT=5432
    
    volumes:
      pgdata:

    저번에 작성했던 docker-compose.yml 파일입니다.

    How to Deploy on Remote Docker Hosts with Docker Compose

    DOCKER_HOST를 명시하면 컴포즈 파일로 간단하게 원격 배포가 가능하다고 합니다.

    DOCKER_HOST="ssh://brokenhinge@dev-machine" docker-compose up -d

    실행했는데 퍼즐을 제대로 못 가져옵니다.

    curl localhost:8080/puzzles
    failed to fetch puzzle

    docker logs nonogram-terminal-server-db-1으로 로그를 확인해 보니 다음 문제가 있었습니다.

    psql:/docker-entrypoint-initdb.d/puzzle_db_dump.sql: error: could not read from input file: Is a directory

    컴포즈 파일의 다음 라인에 문제가 있던 것 같습니다.
    ./puzzle_db_dump.sql:/docker-entrypoint-initdb.d/puzzle_db_dump.sql

    찾아보니이렇게 bind mount를 사용할 경우 호스트 환경에서 puzzle_db_dump.sql 파일을 찾는다고 합니다.
    서버 PC에서 파일을 찾고 있는 거죠.

    파일을 따로 전달해주는 건 귀찮으니, 데이터베이스용 따로 작성해서 이미지 빌드 과정에서 파일을 넣어 주겠습니다.

    # syntax=docker/dockerfile:1
    FROM postgres:14
    
    COPY ./db_scripts /docker-entrypoint-initdb.d/
    services:
      app:
        build: ./app
        ports:
          - 8080:8080
        depends_on:
          db:
            condition: service_healthy
        environment:
          - DB_HOST=db 
          - DB_USER=pz_admin
          - DB_PASSWORD=${DB_PASSWORD}
          - DB_NAME=puzzle_db
          - DB_PORT=5432
      db:
        build: ./postgres
        restart: always
        healthcheck:
          test: ["CMD", "pg_isready"]
          interval: 10s
          timeout: 5s
          retries: 5
        volumes:
          - pgdata:/var/lib/postgresql/data
        environment:
          - POSTGRES_USER=pz_admin
          - POSTGRES_PASSWORD=${DB_PASSWORD}
          - POSTGRES_DB=puzzle_db
          - POSTGRES_PORT=5432
    
    volumes:
      pgdata:

    이제 정상 작동 하네요.

    curl localhost:8080/puzzles
    {"id":817,"title":"고스트 앤 다크니스","author":"soyosun","row_size":20,"col_size":20,"clues":{"col_clues":[[3,5],[3,5,4],[2,12],[3,13,1],[3,12,1],[17,2],[5,12],[4,1,11],[2,2,11],[2,2,2,7],[2,2,1,5],[2,2,7],[3,3,1,4],[3,2,2,5],[3,4,4],[2,3,2,2,4],[2,2,2,7],[3,1,4],[2,3,5],[2,10]],"row_clues":[[8,3],[15],[5,3,2],[6,1,2],[1,2],[1,3,4,3],[2,3,4,2,1],[6,2,1,1],[1,5,1,1,1],[9,5,2],[11,3,2],[9,1,1],[9,3,2],[10,3,2,2],[1,8,1,1,2],[10,2,4],[19],[3,12,1],[2,13,1],[1,11,2]]}}

    서버 열기

    프로그램 배포가 되었으니 본격적인 서버 셀프 호스팅을 시작해줍니다.

    아래 가이드를 거의 그대로 따라했는데요.
    Easy Free and Secure Self-Hosting at Home

    가정용 인터넷이라 IP 고정이 안되기 때문에 DDNS가 필요한데, DuckDNS를 사용해 DDNS 서비스 및 무료 도메인, SSL 인증까지 해결할 수 있었습니다.

    웹 서버로는 SWAG을 사용했습니다.
    Nginx를 기반으로 설정하기 편하게 되어 있어서 초기 단계에서 사용하기 좋을 것 같았습니다.

    기초 설정 템플릿이 잘 나와 있어서 이를 기반으로 nonogram.subfolder.conf 파일을 아래와 같이 작성 해 주었습니다.

    location /nonogram {
        return 301 $scheme://$host/nonogram/;
    }
    
    location ^~ /nonogram/ {
        include /config/nginx/proxy.conf;
        include /config/nginx/resolver.conf;
        set $upstream_app nonogram-terminal-server-app-1;
        set $upstream_port 8080;
        set $upstream_proto http;
        rewrite ^/nonogram/(.*) /$1 break;
        proxy_pass $upstream_proto://$upstream_app:$upstream_port;
    }
        ```
    
    도커 네트워크를 연결 해주었습니다.
    ```bash
    docker network connect nonogram-terminal-server_default swag

    ssl 인증이 안되어서 https 접속이 불가능한 이슈가 생겼습니다.
    SWAG Documentation 을 보니 DuckDNS의 한계로 인해 wildcard subdomain(*.dvbeetle.duckdns.org) 에 대한 인증서가 main url(dvbeetle.duckdns.org) 까지 적용되지 않는다고 합니다.

    그래서 아예 서버 차원에서 서브 도메인 없이 접속 한 경우 www. 로 접속하도록 리디렉션을 걸어버리기로 했습니다.

    # redirect all traffic to https
    server {
        listen 80 default_server;
        listen [::]:80 default_server;
    
        location / {
            return 301 https://www.$host$request_uri;
        }
    }
    
    # WWW redirection
    server {
        listen 443 ssl;
        listen [::]:443 ssl;
        server_name dvbeetle.duckdns.org;
        return 301 https://www.$host$request_uri;
    }
    
    # main server block
    server {
        listen 443 ssl default_server;
        listen [::]:443 default_server;
        server_name www.dvbeetle.duckdns.org;
        # ...
    }

    테스트 해보기

    뭔가 서버 테스트를 해보고 싶습니다. 근데 어떻게 해야될 지 잘 모르겠네요.
    그냥 요청이라도 무작정 많이 보내보고 싶어서 wrk라는 걸 찾아서 써보기로 했습니다.

    wrk -t2 -c10 -d30s https://www.dvbeetle.duckdns.org/nonogram/puzzles
    Running 30s test @ https://www.dvbeetle.duckdns.org/nonogram/puzzles
      2 threads and 10 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency     7.50ms    9.80ms 225.65ms   98.79%
        Req/Sec   735.93     71.28     0.87k    82.64%
      43944 requests in 30.03s, 32.99MB read
      Non-2xx or 3xx responses: 8
    Requests/sec:   1463.15
    Transfer/sec:      1.10MB

    데이터베이스 연결 수를 제한해둬서 서버가 느려질 수 는 있겠습니다만, "Non-2xx or 3xx responses" 는 조금 수상합니다.

    Wireshark로 확인해보니 서버에서 랜덤 row를 못찾았다고 응답하는 경우가 8회 있었습니다.

    이게 로컬 환경에서도 그런지, 파이썬으로 서버가 아닌 DB에 직접 요청을 잔뜩 보내서 확인을 해 보기로 했습니다.
    몇 천번에 한 번 꼴로 TABLESAMPLE BERNOULLI(1)이 실패하는 듯 보였습니다.
    레코드 수가 100개가 넘어서 상관 없을 줄 알았는데요. 그런 원리는 아니였나 봅니다.

    2%로 늘리니 10만번 요청해도 실패 하는 경우가 없었습니다.

    Outro

    다음 편은 아마 서버 api를 좀 더 확장해보는 내용이 되지 않을까 싶습니다.
    클라이언트도 필요한데, C로 되어있는 클라이언트를 쭉 유지보수 할 지, 아니면 더 생산성이 높은 언어로 바꿀 지도 고민이 됩니다.
    3편이 마지막이 되지 않았으면 좋겠는데, 다음 편은 아무래도 시간이 조금 걸릴 것 같네요 ㅎㅎ.

    댓글