Published on

집에서 사용하던 window 데스크탑을 외부접속가능한 서버로 구축하기

Authors

직접 사용하는 Windows 11 PC를 사용가능한 수준의 서버로 만들어 DDNS, 포트포워딩, 인증서 자동 발급 및 CICD까지 구축해보고자 도전해봤습니다.

네트워크 공유기 설정을 변경하고, WAN 연결을 위한 새로운 공유기를 새로 장만하며 공유기 관리자에서 포트포워딩을 진행했습니다.

이 과정을 통해서 네트워크 지식이 좀 더 증가했다고 생각합니다. 서버로 만드는 과정을 A-Z까지 세팅해야하면서 클라우드의 위대함도 알게 되었고, 생각보다 어려움이 많았습니다.

최종적으로 나온 결과물은 WAS 와 DB를 컨테이너로 구축했으며, GITHUB와 Flask 로 CICD서버를 구축하며 local - remote - local배포가 가능하도록 구축했습니다.

개인적으로 가장 시간을 많이 쓰고 어려웠던 부분은 Nginx 와 HTTPS 연결을 위한 nginx 컨테이너를 연결한 부분이었습니다.

정리가 미흡하지만, 나중을 위해 이러한 전 과정을 기록합니다.

1. 외부에서 연결 가능하도록 공유기 설정하기

로컬 컴퓨터를 서버로 쓰기 위한 가장 중요한 단계!

1.1 공유기 구조

저는 집에서 LG U+ 사의 공유기를 사용하고 있었습니다. 그리고 이 공유기에서 유선으로 Windows 11 PC에 연결해서 사용하고 있었습니다.

LG U+ 공유기의 관리자 페이지 IP는 윈도우의 CMD에서 아래 명령어를 통해 확인할 수 있었습니다. 구글링을 해보니 대부분 192.168.219.1 이었고, 저역시도 동일했지만 다를 수도 있기 때문에 직접 확인해보는 것이 정확합니다.

ipconfig

이 LG U+ 공유기의 관리자 페이지에서는 인터넷 연결과 어떤 컴퓨터를 어떻게 연결할 것인지 설정할 수 있습니다. 그리고 직접 컴퓨터를 외부로 노출 가능한 서버로 만들 수 있도록 포트포워딩과 DDNS 설정도 가능합니다.

이 과정에서 DDNS 설정을 외부서비스에 의존해서 IP를 만들 수 있는데, 이 IP는 수시로 변경되는 동적 IP이다보니, DDNS를 통해 고정 도메인을 만들어 주어야 합니다. 하지만, 한달에 한번씩 IP가 바뀌는 경우도 있고, DDNS 서비스에서 제공하는 도메인도 한정적이기 때문에, IPTime 공유기를 통해서 DDNS를 설정해주었습니다.

유튜브에서 따라해본 레퍼런스를 통해서 알게된 것은 IPTime 공유기는 DDNS를 무료로 제공해주고, IPTime 공유기에서 제공하는 도메인으로 고정 IP를 만들 수 있다는 점이었습니다.

1.2 DDNS 설정으로 고정 도메인 얻기

제 서버는 집에 있는 Windows PC이기 때문에, 고정 IP가 아닌 **동적 IP(유동 IP)**를 사용하고 있습니다. 이 경우 IP가 바뀌어도 접속할 수 있도록 **DDNS(Dynamic DNS)**를 사용해야 합니다.

DDNS 주소 설정 IPTime 공유기를 활용하여 hongreat.iptime.org라는 DDNS 주소를 생성했습니다. 이 주소는 제 로컬 컴퓨터의 IP가 바뀌어도 자동으로 갱신됩니다.

DDNS란 Dynamic Domain Name System의 약자입니다.

언제든 변경될 수 있는 동적 IP 주소에 도메인 이름을 부여하여, IP 주소가 변경되어도 도메인 이름으로 접근할 수 있도록 해주는 서비스입니다.

  • 메인 공유기: 직접 인터넷을 결제하며 외부에서 인터넷 연결이 가능하도록 만들어주는 LG U+ 공유기
  • 서브 공유기: 메인 공유기에서 인터넷을 받아와서 사용할 수 있게 해주고, 데스크탑이 서버가 될 수 있도록 외부로 연결시켜주는 IPTime 공유기

1.3 LG U+ 공유기에서 포트포워딩

LG U+(이하 LG) 공유기에서 포트포워딩을 설정하는 방법은 다음과 같습니다.

대부분 비슷할텐데, 이 IP 를 통해서 공유기의 관리자에 접속해줍니다. 이곳에서도 DDNS(외부)서비스를 가입하고 서드파티에 의존한 무료 도메인을 얻을 수 있습니다. 하지만, SSL 설정과 해당 도메인을 장기적으로 사용하려면 돈을 지불해야한다는 사실을 알고, 다른 방법을 찾기로 했습니다.

1.4 IPTimeDDNS 설정 (자체 DDNS 사용)

공유기의 외부 IP는 유동 IP이기 때문에, 시간이 지나면 바뀔 수 있습니다. 그렇기 때문에 이를 해결하기 위해 DDNS(Dynamic DNS) 기능을 활용해, 고정된 도메인 주소로 접근할 수 있도록 해줘야합니다.

1.4.1 DDNS등록

  • 브라우저에서 192.168.0.1 접속 → 관리자 로그인
  • 왼쪽 메뉴에서 고급 설정 → 특수기능 → DDNS 설정
  • 서비스 제공자: ipTIME DDNS (이것 때문에 IPTime 공유기를 구매한 것)
  • 도메인 이름: 원하는 주소 입력 (예: hongreat.iptime.org)
  • [등록] 버튼 클릭

이렇게 함으로써 이 도메인은 이제 외부에서 https://hongreat.iptime.org로 접속하면 내 공유기에 접근하게 되는 주소가 되는 것 입니다.

1.4.2 포트포워딩 설정 (IPTime 공유기)

Django 앱 또는 기타 WAS에 접속하기 위해 포트를 개방해줍니다.

  1. 좌측 메뉴에서 고급 설정 → NAT/라우터 관리 → 포트포워드 설정
  2. [추가] 버튼을 눌러 아래 정보 입력합니다.
항목값 예시
규칙 이름django-https (자유롭게)
내부 IP 주소192.168.0.101 (로컬 서버 주소)
프로토콜TCP
내부 포트443
외부 포트443

포트 80 (Let’s Encrypt 인증서 발급용)도 동일하게 추가 설정해야 합니다.

1.4.3 LG U+ 공유기에서 DMZ 설정하기 (IPTime 공유기 외부노출)

  • DMZ 설정이란?

LG U+ 공유기에서 DMZ(실제관리자에서는 SuperDMZ이라는 명칭을 사용.) 설정을 통해 IPTime 공유기를 연결해줘야 합니다. DMZ는 공유기 에 연결된 장비들 중에서 특정 장비를 외부에 완전히 노출시키는 설정입니다. 보안상 모든 포트가 개방되는 위험이 있지만, IPTime 공유기 그리고 서버로 쓰여질 컴퓨터에 방화벽과 포트포워딩 설정이 있다면 비교적 안전하게 운영할 수 있습니다.

  1. 브라우저에서 192.168.219.1 접속 (U+ 공유기 기본 IP) 및 로그인
  2. 메뉴 → 고급 설정 → 보안 설정 또는 방화벽 설정 → DMZ 설정으로 이동
  3. DMZ IP 주소: IPTime 공유기의 내부 IP 입력 (예: 192.168.219.102)

이때 IPTime 공유기의 IP는 LG U+ 공유기에서 할당받은 주소여야 합니다. 예를 들어 LG U+ 공유기 DHCP 목록에서 192.168.219.102로 할당되었다면, 이 주소를 설정해줘야 하는 것입니다.

이제 외부에서 아래 주소로 접근이 가능해야하며 만약 접속이 되지 않는다면 문제가 있는 것 입니다.

1.5 테스트: iptime DDNS 접속 확인

로컬 컴퓨터에서 실행한 DJANGO 를 외부에서 접속가능한지 확인했습니다. (DJANGO 는 기본적으로 8000포트를 사용하기 때문에 뒤에 포트번호를 함께 접속하면 접속이 가능합니다.)

다른 애플리케이션 어떤 것이어도 좋으나, 실행가능한 세팅까지 도달했는지 확인해야 합니다.(로직없는 빈 페이지라도 실행은 되어야함.)

이제 DDNS(http://hongreat.iptime.org:8000)로 접속을 시도해보면 8000포트로 연결되어 있는 나의 Django웹 애플리케이션 서버를 확인할 수 있습니다.

1.6 API 도메인 설정 – DDNS와 CNAME 레코드 연결하기

HTTPS를 적용하기 위해서는 공인 도메인이 필요합니다.

이전에 이미 가비아에서 hongreat.co.kr이라는 도메인을 구매한 상태였고, 이 도메인의 서브도메인인 something_api.hongreat.co.kr을 사용하기로 결정했습니다.

하지만 HTTPS 인증서를 발급받기 위해서는 자신이 소유한 도메인이 필요합니다. 따라서 hongreat.iptime.org로 직접 연결하지 않고, 제가 소유한 something_api.hongreat.co.kr이 이 주소를 참조하도록 설정하였습니다.

가비아에서 CNAME 레코드 설정하기 가비아 도메인 관리 페이지에서 다음과 같이 DNS 레코드를 추가했습니다

호스트 이름에 something_api를 입력하면, something_api.hongreat.co.kr 도메인이 구성됩니다.

값으로는 IPTime DDNS 주소인 hongreat.iptime.org를 입력합니다.

이렇게 설정하면 브라우저에서 something_api.hongreat.co.kr으로 접속 시 실제로는 제 로컬 컴퓨터 IP로 트래픽이 전달되게 됩니다.

2. 애플리케이션 서버 구축하기

Django를 메인서버에서 주로 사용할 애플리케이션이라고 가정하고 이 프로젝트를 기획했습니다. 포트포워딩과 CICD 파이프라인을 잘 구축한다면 다른 어떤 프레임워크를 사용해도 상관 없습니다.

2.1 docker-compose.yml 작성

docker-compose.yml은 다양한 서비스들을 함께 사용할 것 이기 때문에, 사용하는 것을 추천합니다.

가장 쉬운 것은 Postgresql 을 사용하는 것이고, 필요하다면 redis나 celery 등의 컨테이너도 쉽게 추가가 가능합니다.

2.2 docker-compose.yml을 통한 서비스 정의 – nginx 연동과 네트워크 구조 중심

Docker Compose를 이용해 Django 애플리케이션을 서비스 단위로 정의할 수 있으며, 아래는 실제로 사용한 docker-compose.yml 파일의 주요 구성입니다.

services:
  web:
    build: .
    command: gunicorn src.config.wsgi:application --bind 0.0.0.0:8000 --reload
    volumes:
      - .:/app
    expose:
      - "8000"  # 외부 노출은 없지만 nginx-proxy가 내부적으로 접근 가능하도록 열어둡니다
    env_file:
      - .env
    environment:
      - VIRTUAL_HOST=something_api.hongreat.co.kr
      - VIRTUAL_PORT=8000
      - LETSENCRYPT_HOST=something_api.hongreat.co.kr
      - LETSENCRYPT_EMAIL=hongreat95@gmail.com
    networks:
      - default
      - nginx-proxy
    depends_on:
      - db
      - redis
      - rabbitmq

이 설정에서 핵심이 되는 부분은 다음과 같습니다.

2.2.1 VIRTUAL_HOST, LETSENCRYPT_HOST, LETSENCRYPT_EMAIL

이 세 가지 환경변수는 nginx-proxy와 letsencrypt-nginx-companion이 자동으로 도메인을 인식하고 프록시 및 HTTPS 인증서를 설정하는 데 사용됩니다.

  • VIRTUAL_HOST: nginx가 라우팅할 도메인
  • VIRTUAL_PORT: 내부 서비스의 실제 포트 (여기서는 8000)
  • LETSENCRYPT_HOST: 인증서 발급을 위한 도메인
  • LETSENCRYPT_EMAIL: 인증서 만료 알림을 받을 이메일

이 환경변수를 통해 도커 컨테이너가 뜨자마자 nginx가 자동으로 해당 도메인을 프록시 설정에 추가하고, 인증서도 자동으로 발급 요청을 보냅니다.

2.2.2 expose vs ports

web 서비스에서는 ports가 아닌 expose를 사용했습니다.

이는 컨테이너 외부에서는 포트를 직접 열지 않되, 같은 Docker 네트워크 내부에서는 접근 가능하도록 하기 위한 설정입니다.

nginx-proxy가 동일 네트워크에 있기 때문에, 이 8000 포트를 통해 내부적으로 접근할 수 있습니다.

2.2.3 networks 설정

networks:
  default:
    name: app_network
  nginx-proxy:
    external: true

default 에서 Django와 관련된 내부 컨테이너들 간 통신을 위한 기본 네트워크를 정의하는데, DB, Redis 등과의 연결에서 공통적으로 사용합니다.

nginx-proxy: nginx-proxy 컨테이너와 통신하기 위한 외부 네트워크입니다.

nginx-proxy는 다른 프로젝트들과 공유할 수 있기 때문에 external: true로 설정하여 독립된 네트워크로 분리됩니다.

Django 웹 컨테이너는 default, nginx-proxy 두 네트워크 모두에 연결되어 있기 때문에, 내부적으로는 데이터베이스와 통신하고, 외부적으로는 nginx-proxy를 통해 접근이 가능하게 됩니다.

3. Flask로 CICD 서버 구축하기

로컬에서 개발 중인 Django 애플리케이션을 GitHub와 연동하여, 코드 푸시 시 자동으로 배포까지 이어지는 간단한 CICD 파이프라인을 구축했습니다.

Flask를 사용한 특별한 이유는 없고, 간단한 Webhook 서버를 만들고 GitHub에서 push 이벤트가 발생할 때마다 해당 서버가 배포 스크립트를 실행됩니다.

3.1 Flask로 Webhook 수신 서버 만들기

가볍게 실행할 수 있는 Flask 앱으로 GitHub Webhook 요청을 처리했습니다. 아래는 실제 사용한 app.py입니다.

from flask import Flask, request
import hmac, hashlib, subprocess

app = Flask(__name__)
SECRET = b'GitHub Webhook에 설정한 secret과 동일해야 함'

@app.route('/webhook/', methods=['POST'])
def webhook():
    signature = request.headers.get('X-Hub-Signature-256')
    if signature is None:
        return 'Missing signature', 403

    sha_name, signature = signature.split('=')
    mac = hmac.new(SECRET, msg=request.data, digestmod=hashlib.sha256)
    if not hmac.compare_digest(mac.hexdigest(), signature):
        return 'Invalid signature', 403

    subprocess.Popen(["bash", "./deploy.sh"])  # 배포 스크립트 실행
    return 'OK', 200

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)  # Flask를 외부에서 접근 가능하도록 설정

X-Hub-Signature-256 헤더를 통해 요청의 진위 여부를 검증합니다.

GitHub Webhook에서 설정한 secret key와 일치하지 않으면 배포를 차단합니다.

보안을 위해 반드시 HTTPS 환경에서 사용할 것을 권장합니다.

3.2 배포 스크립트 작성 (deploy.sh) 실제 서버에 반영되는 모든 명령어를 deploy.sh에 작성해놓고, webhook 수신 시 이를 실행합니다.

#!/bin/bash

echo "==== 배포시작 ===="

cd /mnt/c/Users/유저명/django폴더명 || {
    echo "프로젝트 디렉토리 이동 실패"
    exit 1
}

git pull origin main

docker compose down
docker compose up -d --build

echo "==== Migrate 시작 ===="
docker compose exec web python src/manage.py migrate

echo "==== Deploy & Migrate Complete ===="
git pull: 최신 코드를 가져오고,

docker compose up -d --build: 이미지 재빌드 및 컨테이너 재실행,

manage.py migrate: DB 마이그레이션까지 자동 수행합니다.

주의: deploy.sh는 운영환경 명령어를 포함하므로, 권한 설정과 위치에 주의해야 합니다. 또한 서버가 여러 명령을 순차적으로 실행하는 만큼, 경로 에러나 docker 오류가 발생하지 않도록 사전 점검이 필요합니다.

3.3 GitHub Webhook 설정하기 — 코드 푸시만으로 배포 자동화하기

Flask 서버를 만들고 배포 스크립트를 작성했다면, 이제 GitHub에서 해당 서버로 Webhook을 보내도록 설정해야 합니다.

처음에는 저도 GitHub에 이런 기능이 있는지 몰랐습니다. 그런데 알고 보니 GitHub에는 코드 푸시, 이슈 생성, PR 등 다양한 이벤트가 발생할 때 외부 서버로 알림을 보내주는 Webhook 기능이 내장되어 있었습니다.

이 기능을 활용하면, 코드 푸시 → 서버 자동 배포로 이어지는 CICD를 구축할 수 있습니다.

3.3.1 GitHub에서 Webhook 설정하는 방법

GitHub 레포 → Settings → Webhooks → Add webhook 클릭

Payload URL	http://webhook.hongreat.co.kr/webhook/
Content type	application/json
Secret	Flask에서 설정한 값과 동일해야 함
Events	Just the push event
GitHub Webhook 설정 화면 설정을 저장하면,코드가 push될 때마다 자동으로 Flask 서버에 HTTP 요청이 전송되고, 그에 따라 deploy.sh가 실행되어 배포가 이루어지게 됩니다.

4. Nginx로 HTTPS 연결하기

이번에는 로컬 서버에서 운영 수준의 HTTPS 보안을 적용하기 위한 구성 과정을 설명드리겠습니다.

nginx-proxy와 letsencrypt-nginx-companion을 활용하면, 인증서 발급과 HTTPS 구성까지 자동화할 수 있습니다.

4.1 nginx-proxy + letsencrypt-nginx-companion 소개

Docker 환경에서 nginx-proxy는 각 컨테이너의 환경변수를 감지하여 자동으로 reverse proxy 설정을 생성해주는 이미지입니다.

여기에 letsencrypt-nginx-companion을 함께 사용하면, Let's Encrypt 인증서도 자동으로 발급 및 갱신할 수 있습니다.

이 두 컨테이너가 함께 동작하면 다음과 같은 구조로 HTTPS 환경을 구성할 수 있습니다:

컨테이너가 뜰 때 VIRTUAL_HOST, LETSENCRYPT_HOST, LETSENCRYPT_EMAIL 등의 환경변수를 감지

자동으로 nginx 설정 및 SSL 인증서 발급 요청

인증서 갱신도 주기적으로 자동 처리

4.2 nginx-proxy 전용 docker-compose 구성

아래는 nginx-proxy와 letsencrypt를 포함한 docker-compose.yml 예시입니다.

services:
  nginx-proxy:
    image: jwilder/nginx-proxy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./vhost.d:/etc/nginx/vhost.d
      - ./html:/usr/share/nginx/html
      - ./certs:/etc/nginx/certs:ro
      - /var/run/docker.sock:/tmp/docker.sock:ro
    networks:
      - nginx-proxy

  nginx-letsencrypt:
    image: jrcs/letsencrypt-nginx-proxy-companion
    environment:
      - NGINX_PROXY_CONTAINER=nginx-proxy
    volumes:
      - ./vhost.d:/etc/nginx/vhost.d
      - ./html:/usr/share/nginx/html
      - ./certs:/etc/nginx/certs
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - nginx-proxy

networks:
  nginx-proxy:
    external: true

이 설정에서 가장 중요한 핵심은 volumes에 연결된 디렉토리들입니다. 각각의 디렉토리는 nginx 및 인증서 발급과 갱신에서 필수적인 역할을 합니다.

volumes:
  - ./vhost.d:/etc/nginx/vhost.d
  - ./html:/usr/share/nginx/html
  - ./certs:/etc/nginx/certs
  - /var/run/docker.sock:/var/run/docker.sock:ro

4.2.1 vhost.d

./vhost.d:/etc/nginx/vhost.d

nginx-proxy는 도메인별로 자동으로 nginx 가상 호스트 파일을 생성합니다.

이 설정은 생성된 도메인 별 nginx 설정 파일을 vhost.d 디렉토리에 저장하게 해주며, 이후 인증서 companion 컨테이너도 이 설정에 접근해야 하므로 양쪽 컨테이너에서 공유합니다.

이것을 통해 nginx-proxy는 도메인에 대한 구체적인 프록시 설정을 생성합니다.

4.2.2 html

./html:/usr/share/nginx/html

Let’s Encrypt는 인증서 발급 시 .well-known/acme-challenge/ 경로로 HTTP 요청을 보냅니다.

이 경로는 nginx가 직접 정적 파일로 서빙해야 하며, 실제로는 html 디렉토리 아래에 해당 challenge 파일이 임시로 생성됩니다.

이 볼륨이 없으면 nginx가 이 경로를 처리하지 못하고, 요청이 Django 앱으로 넘어가면서 404 오류가 발생합니다.

4.2.3 certs

./certs:/etc/nginx/certs

인증서 발급이 완료되면 Let’s Encrypt companion은 cert.pem, privkey.pem 등의 인증서 파일을 이 디렉토리에 저장합니다.

nginx-proxy는 해당 경로에서 인증서를 자동으로 로딩하여 TLS를 구성합니다.

이 볼륨이 없으면 인증서를 저장할 수 없어 HTTPS 설정이 작동하지 않으며, nginx도 TLS 종료를 할 수 없습니다.

4.2.4 docker.sock

/var/run/docker.sock:/tmp/docker.sock:ro

nginx-proxy와 letsencrypt-nginx-companion은 도커 API를 직접 읽어서 **현재 실행 중인 컨테이너들의 환경변수(VIRTUAL_HOST 등)**를 감지합니다.

이를 통해 도메인 라우팅과 인증서 발급 대상을 자동으로 인식합니다.

이 볼륨을 통해 docker.sock 파일을 읽기 전용(ro)으로 공유해야 자동 감지 기능이 작동합니다.

이제 .well-known 경로가 정상적으로 응답하게 되어 인증서 발급이 성공합니다.

4.3 도메인 기반 환경변수 (VIRTUAL_HOST, LETSENCRYPT_HOST)

Django 컨테이너 혹은 다른 웹 애플리케이션 컨테이너에 아래와 같은 환경변수를 설정해주어야 합니다.

environment:
  - VIRTUAL_HOST=호스트
  - VIRTUAL_PORT=8000
  - LETSENCRYPT_HOST=호스트
  - LETSENCRYPT_EMAIL=이메일

  • VIRTUAL_HOST: nginx-proxy가 인식할 도메인
  • VIRTUAL_PORT: 실제 서비스가 실행 중인 내부 포트
  • LETSENCRYPT_HOST: 인증서가 적용될 도메인
  • LETSENCRYPT_EMAIL: 인증서 관련 알림이 갈 이메일 주소

이 환경변수들로 인해 nginx와 인증서 발급 요청이 자동으로 이루어집니다.

4.4 인증서 발급이 에러와 함께 실패되는 경우

인증서 발급 과정에서 도커 볼륨설정 이슈를 포함해서 다음과 같은 문제들을 직접 겪었지만 캡쳐를 하지는 못했고, 간단하게 기록합니다.

  • Invalid response from /.well-known/acme-challenge/...: 404

    • nginx가 인증 요청을 Django 컨테이너로 라우팅해버려서 정적 경로를 찾지 못하는 문제
    • nginx-proxy와 letsencrypt 컨테이너의 vhost.d, html, certs 볼륨이 모두 정확히 마운트되었는지 확인해야 합니다.
  • Too many failed authorizations (rate limit)

    • 여러 번 인증 실패가 누적되어 Let's Encrypt에서 10분에서 최대 한 시간 동안 차단됨
    • 설정을 고친 후 최소 10분에서 15분이상 대기, 그리고 재시도 했습니다.

이러한 실수들을 겪으며 nginx와 인증서 발급 로직을 깊이 이해할 수 있었습니다. 특히 .well-known/acme-challenge 경로가 nginx 내부에서 어떻게 처리되는지를 파악하는 것이 중요했습니다.

4.5 API 요청 성공

최종적으로 외부 IP 에서 API를 요청했을 때 아래와 같은 사항을 모두 확인할 수 있었고 이는 정상적으로 작동하는 것 입니다. (보안적으로 개선할 부분이 많지만 네트워크 구성과 인증서 발급, CICD파이프라인까지 완료되었습니다.)

  • HTTPS 인증서가 정상적으로 적용되어 자물쇠 아이콘이 표시되기 시작했습니다.(인증서는 Let's Encrypt에서 발급됩니다.)
  • http 접속 시 https로 자동 리다이렉션 처리됩니다.
  • Django 관리자 페이지까지 보안 연결을 통해 접근이 가능합니다.
  • hongreat 블로그의 글을 봐주셔서 감사합니다!^^
  • 내용에 잘못된 부분이나 의문점이 있으시다면 댓글 부탁 & 환영 합니다~!
  • (하단의 버튼을 누르시면 댓글을 보거나 작성할 수 있습니다.)
Buy Me A Coffee