Post

[CICD] nginx, cloudflare, https, docker compose, github action 적용기

HTTPS ?

보통 Springboot 를 서버를 개발하면 기본적으로 HTTP 통신을 한다. 하지만 어느순간 HTTPS 통신 필요해질때가 온다. 그것이 보안성때문이든, 기능성때문이든 여러이유든 간에 말이다. 나 같은 경우에는 안드로이드와 백엔드 통신을하는데 HTTP 로 통신하면 안드로이드에서 별도의 설정이 필요했다. 그래서 보안성과 편리성을 챙기기위해 HTTPS 로 전환하기로 했다. 이를 적용하는데 두 가지방식으로 Cloudflare 를 사용하는방식과, Certbot + Let’s encrypt 방식으로 적용해봤는데 둘 다 소개할 예정이다. 글을 쓰다가 길어져서 여기서는 Cloudflare 를 이용한 방법만 소개한다.

Architecture

우선 어떻게 적용하는지 앞서 내 프로젝트 아키텍처에 대해 소개하겠다.

이미지

Github Action 을 이용하여 CICD 를 구축했으며, EC2 에 Springboot 를 Docker 를 이용해 띄워 서버를 운용하고 있었다. 여기서 나는 서버앞단에 Nginx 를 docker 로 띄워 배치할 예정이다. 그리고 nginx 설정은 변경이 잦을 수 있기에 github 에 올려서 관리하고, Github Action 을 이용해 서버에 배포한다. Cloudflare 를 사용하면 Nginx 앞에 배치하게 된다.

준비물

도메인주소가 필요하다. 도메인 주소란, ip 로 통신하면 클라이언트는 해당 ip 를 외워서 접속해야되는데 이는 가독성이 떨어지기때문에 도메인을 설정한다. 예를 들어 네이버 도메인은 naver.com 이다. 실제 네이버의 아이피 주소는 125.209.222.141 이다.

해당 도메인 서비스를 제공하는 업체는 많으며 대표적으로 가비아가 있다. 무료로 제공하는데도 많지만, 가비아에서도 1년동안만은 저렴한 가격에 사용할 수 있어서 나는 가비아에서 도메인을 구매했다.

편한 예시를 위해 구매한 도메인 이름을 “example.com” 이라 하겠다. 그리고 ip 를 “13.24.32.55” 라고 하겠다.

Cloudflare

나는 Cloudflare 를 처음 사용했다. Cloudflare 는 세계적으로 CDN 서비스를 제공하는 업체이다. 캐싱기능, SSL/TLS, DDoS 방지, 악성 봇 차단, 로드밸런싱, 모니터링 등 매우 다양하고 유용한 기능을 제공한다. 그리고 일부 기능들을 무료로 사용할 수 있다. 내가 Cloudflare 찾은 가장 큰 이유는 아주 간편한 설정으로 SSL/TLS 를 사용할 수 있다는 점이다.

하지만

Cloudflare 에는 엄청난 단점이 있다. 무료버전은 리전을 선택할 수 없고, 그 리전은 무조건 한국이 아닌 미국이나 일본 등 외국이라는 점이다. 그래서 통신과정이 느리다.

기대했던 통신 : 클라이언트(한국) -> Cloudflare(한국) -> Server(한국) -> Cloudflare(한국) -> 클라이언트(한국) 실제 통신 : 클라이언트(한국) -> Cloudflare(미국) -> Server(한국) -> Cloudflare(미국) -> 클라이언트(한국)

응답을 받는때까지 해외를 2번이나 갔다오다보니 매우 느리다. 실제로 적용해봤더니 매우 느리다. 10배 정도 느림을 체감할 수 있다. 페이지가 로딩되는데 1초정도 걸린것 같았다.

그래서

Cloudflare 는 언제 사용하는가? Cloudflare 는 Enterprise 유료버전을 사용하면 한국 리전을 제공한다. 그러니 돈이 많을때 사용하면 아주 좋은 선택지가 될것이다… 하지만 가난한 학생에게는 어림도 없다. 그 외에도 속도가 그렇게 중요하지 않고 간단하게 구축하는 프로젝트라면 사용하는걸 추천한다. Cloudflare 를 사용하면 아주 간단히 SSL/TLS 적용이 가능하니까 말이다.

HTTPS 적용 방법

Architecture

Cloudflare 를 사용한다면 아키텍처가 아래와 같다.

이미지

Cloudflare 를 사용한다면 Nginx 앞단에 배치해서 사용해야한다. 그 이유는 Cloudflare 는 DNS, SSL 제공하는데 DNS 는 naver -> 125.209.222.141 로 바꿔주는 역할이다. 근데 딱 거기까지만이다. 포트번호까지 달아주는게 아니라는 것이다. 그래서 Nginx 를 이용해 포트번호까지 붙여줘야 한다. 즉 125.209.222.141 -> 125.209.222.141:8080 으로 바꿔주는 역할이다.

네임서버 등록

Cloudflare 를 사용한다면, 도메인을 구입한 곳에서 Cloudflare 네임 서버를 등록해주어야 한다. 네임서버는 도메인을 아이피로 바꿔주는곳이다. 가비아에서 도메인을 구매했다면 기본적으로 가비아 네임서버가 등록된다. 가비아가 도메인을 아이피로 바꿔주기 때문이다. 하지만 Cloudflare 를 사용한다면, 도메인을 아이피로 바꾸는과정을 Cloudflare 가 담당하기 때문에 Cloudflare 네임서버를 등록해주어야 한다.

Cloudflare 에 가입하고 구입한 도메인주소를 넣으면 아래와같이 Cloudflare 네임 서버를 알려준다.

이미지

이것을 가비아에 들어가 적어주면 된다.

이미지

저장을 하면 적용되는데 시간이 좀 걸린다. 보통 1~2시간이며 길면 2일정도도 걸린다고 한다.

DNS 레코드 등록

네임서버가 적용되면 Cloudflare 페이지가 바뀌면서, 여러기능을 사용할 수 있다. 그러면 DNS 레코드를 등록해야 된다. 주로 A, AAAA, CNAME 을 사용하는데 나는 A 만 설정해주었다. A 는 주소와 ip 가 1:1 맵핑된다.

이미지

이렇게 설정해주었다. 보통 @(루트), www 를 지정해두는것 같다. 루트는 example.com 을 가리키며, www 만 적으면 www.example.com 을 가리킨다. 나는 example.com 은 운영서버에 사용할거고, 서브도메인으로 개발서버에 사용할려고 dev.example.com, www.dev.example.com 도 등록해주었다. 참고도 이 글도 dev.example.com 을 https 적용하는 것을 설명한다.

SSL/TLS 설정

Cloudflare 에서 SSL/TLS 탭으로 들어가 설정을 해주면 된다.

이미지

사진을 보면 가변과 전체가 있는데 현재 내 아키텍처로 설명하자면

  • 가변은 클라이언트와 Cloudflare 사이는 https, Cloudflare 와 Nginx 사이는 http 로 통신한다.
  • 전체, 전체(엄격) 은 클라이언트와 Cloudflare 사이, Cloudflare 와 Nginx 사이 모두 https 로 통신한다.

가변을 사용하면 겉으로는 https 를 사용하기때문에 당장 급하고, 복잡하게 설정할 필요없다면 사용할 수 있다. 가변을 선택하면 인증서와 개인키를 만들필요가 없기에 훨씬 간단해진다. 하지만 보안성은 취약하고 클라이언트가 위험성을 인지하기 어렵기에 가변은 왠만하졈 사용하지 말자. 전체와 전체(엄격) 의 차이는 Cloudflare 에서 제공하는 인증서를 사용하는지 여부다.

인증서, 개인키 생성

전체(엄격)을 사용한다면 Cloudflare 탭에 “원본 서버” 를 클릭하고 인증서를 생성하자. 인증서를 생성하면 원본인증서값과 개인 키 값을 아래와 같이 알려준다. 복사해서 키를 만들어두자. 해당 페이지를 나가면 인증서값을 안알려주니 꼭 만들어두자.

이미지

여기까지 했다면 Cloudflare 설정은 끝이다. 그 다음은 NGINX 설정으로 가보자.

NGINX

NGINX 는 웹서버로 로드밸런싱, 정적파일 캐싱, 포트포워딩 등 수 많은 기능을 제공한다. 여기서는 포트포워딩 기능을 사용해 Cloudflare 로 부터 받은 ip 주소에 포트를 붙여주자. NGINX 전체 설정은 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
http {

    # -------------------- spring-boot-dev WAS --------------------
    # upstream backend 는 상수라고 보면된다. backend 키워들 쓰면 back:8080 인 것이다.
    upstream backend {
        server back:8080; # back:8080 을 정의한다. 여기서 back 은 뒤에 나올 docker-compose 의 서비스명이다. ip 를 적어서 통신을 하게해도 된다.
    }

    # Redirect all traffic to HTTPS
    server {
        listen 80; # 80 포트는 HTTP 를 의미한다.
        server_name dev.example.com; # 적용시킬 도메인 주소를 의미한다. 당연히 여기에는 여러분의 도메인 주소를 적어야한다.

        return 308 https://$host$request_uri; # HTTP 로 오면 그대로 HTTPS로 리다이렉트 한다는 뜻이다.
    }

    server {
        listen 443 ssl; # 443 포트는 HTTPS 를 의미한다.
        server_name dev.example.com; # 적용시킬 도메인 주소를 의미한다.

        ssl_certificate /etc/cloudflare/origin_cert.pem; # 인증서가 저장된 위치를 적어준다.
        ssl_certificate_key /etc/cloudflare/private_key.pem; # 개인키가 저장된 위치를 적어준다.

        location / {
            proxy_pass http://backend; # 위에서 정의한 http://back:8080 으로 보낸다.
            proxy_set_header Host $host; # 클라이언트가 요청한 호스트의 도메인
            proxy_set_header X-Real-IP $remote_addr; # 클라이언트의 실제 IP 주소
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 원격 클라이언트의 실제 IP 주소와, 이전에 거친 프록시 서버의 IP 주소들이 쉼표로 구분되어 포함
        }
    }
}

Docker-compose

docker-compose 설정은 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
services:
  back: # docker-compose 서비스명, 앞서 nginx.conf 에서말한 서비스명이 이것이다.
    image: [dockerhub_id]/back:latest # 이미지 이름
    container_name: back # 실행될 컨테이너명
    ports:
      - 8080:8080 

  nginx: 
    image: nginx:1.25-alpine
    container_name: nginx
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf # EC2 에 있는 nginx 설정을 컨테이너로 복사합니다
      - ./cloudflare/origin_cert.pem:/etc/cloudflare/origin_cert.pem # EC2 에 있는 인증서를 컨테이너로 복사합니다.
      - ./cloudflare/private_key.pem:/etc/cloudflare/private_key.pem # EC2 에 있는 개인키를 컨테이너로 복사합니다.
    ports: # nginx 는 http, https 로 주고받으니 해당 포트를 열어둔다.
      - 80:80  
      - 443:443
    depends_on:
      - back # back 서비스가 실행된후 시작한다.

EC2 서버에는 nginx/nginx.conf, /cloudflare/origin_cert.pem, /cloudflare/private_key.pem 가 필요하다.

나는 nginx/nginx.conf 는 Github Action CD 과정에서 EC2 로 보내주도록 해놨다. Github Action CD 과정에서 아래코드를 추가하면 된다.

1
2
3
4
5
6
7
8
9
10
11
# 설정 파일 서버(EC2)로 전달
- name: Send docker-compose.yml and nginx.conf
uses: appleboy/scp-action@master
with:
    username: ubuntu
    host: ${{ secrets.KCS_HOST_DEV }}
    key: ${{ secrets.KCS_KEY_DEV }}
    source: "src/main/resources/backend-submodule/docker-compose-dev.yml,./nginx/nginx.conf"
    target: "/home/ubuntu/"

무슨 Github Aciton 인지 잘모르겠다면 [CICD] Spring boot 환경 Github Action 에서 Docker, Docker-compose, EC2, RDS 적용기 글을 참고하면 좋다. 나는 Github Action CD 가 필요없고, 당장 적용해보고싶다면 ~/nginx/nginx.conf 를 EC2 에서 만들어주면 된다.

/cloudflare/origin_cert.pem, /cloudflare/private_key.pem 는 Cloudflare 에서 만든 인증서, 개인키값이다. 나는 인증서는 origin_cert.pem, 개인키는 private_key.pem 이라고 이름을 지어줬다. 보통 개인컴퓨터에서 해당 키를 만들어뒀을텐데 이를 EC2 서버에 보낼려면 scp 명령어를 사용하면 된다. 아래는 예시다.

scp -i “backend.pem” private_key.pem ubuntu@ec13-24-32-55.ap-northeast-2.compute.amazonaws.com:~/cloudflare/

이렇게 키를 EC2 서버에 보내 필요한 파일을 다 갖추었다면 docker-compose 를 실행시키자. 잘 적용되었다면 여러분은 https://dev.example.com 에 접속할 수 있을것이다.

참고로

1
2
3
4
volumes:
    - ./nginx/nginx.conf:/etc/nginx/nginx.conf # EC2 에 있는 nginx 설정을 컨테이너로 복사합니다
    - ./cloudflare/origin_cert.pem:/etc/cloudflare/origin_cert.pem # EC2 에 있는 인증서를 컨테이너로 복사합니다.
    - ./cloudflare/private_key.pem:/etc/cloudflare/private_key.pem # EC2 에 있는 개인키를 컨테이너로 복사합니다.

docker-compose 파일은 EC2 서버에서 실행된다. 그러니 기본위치가 ~(home) 으로 되어있다. 여기서 -./nginx/nginx.conf:/etc/nginx/nginx.conf 의 의미는 ~/nginx/nginx.conf 에있는 파일을 컨테이너 내부에 /etc/nginx/nginx.conf 위치로 복사하겠다는 뜻이다.

alpine 이미지

1
image: nginx:1.25-alpine

나는 nginx 의 경량화된 alpine 이미지를 사용했다. 보통 nginx 이미지가 180MB 정도하는데 alpine 이미지는 50MB 정도 한다. 상당히 많이 경량화되니 기능에 문제가 없다면 사용하는것을 추천한다.

보안설정

혹시라도 EC2 서버 보안그룹에 80, 443 포트를 안열어놨다면 열어두자.

Trouble Shooting - Directive is not allowed

이미지

docker-compose 사용할 때 nginx.conf에서 ‘directive is not allowed’ 에러가 발생한다. 항상 첫라인에서 안된다고 에러가 난다. nginx.conf 에서 첫라인에 worker_processes 를 사용하면 위와 같이 worker_processes 가 허용되지 않았다고 에러가 나고, 이 글에서 설정한대로 하면 http 가 허용되지 않았다고 에러가 난다.

에러가 난 docker-compose 에서는 아래와 같이 폴더를 옮기는 방식이었는데

1
2
volumes:
    - ./nginx:/etc/nginx # EC2 에 있는 nginx 설정을 컨테이너로 복사합니다

아래와 같이 파일명까지 명시해주니 에러가 나지 않았다.

1
2
volumes:
    - ./nginx/nginx.conf:/etc/nginx/nginx.conf # EC2 에 있는 nginx 설정을 컨테이너로 복사합니다

Trouble Shooting - 리디렉션이 너무 많습니다.

여러분이 설정을 완료하고 https://dev.example.com 으로 접속했더니 리디렉션이 너무 많습니다 라는 문구가 뜨면서 접속이 안될 때가 있다. 원인이 많지만 대부분 설정을 잘못해서이다. 나같은 경우에는 nginx.conf 에서 proxy_pass http://backend;proxy_pass http://back; 로 잘못적어서 발생했었다.

Reference

클라우드 플레어 언제사용할까
클라우드 플레어 SSL 적용
Directive is not allowed 해결
클라우드 플레어, nginx 사용하기
nginx 기초

This post is licensed under CC BY 4.0 by the author.