워드프레스 블로그에 SSL 적용하기

4년간 방치해둔 블로그를 버릴까도 생각했지만 회사도 그만둔 김에 놀면 뭐하나… 일단 시대에 맞게 HTTPS로 서빙을 해보기로 마음먹고 시도해봤다. 일단 내가 운영하는 워드프레스 서빙 환경은 다음과 같다.

  • AWS EC2에 도커 환경구성
  • AWS EC2에 Nginx 설치하고 여러 도커 컨테이너를 멀티 호스팅

일단 SSL 인증서가 필요하다. 무료 인증서를 받기 위해 EC2에 Certbot 을 설치한다.

  • sudo yum install epel-release
  • sudo yum install certbot python3-certbot-nginx

Let’s Encrypt 에서 SSL 인증서 받기

  • sudo certbot --nginx -d miconblog.com

이렇게 서트봇을 이용해 인증서를 발급받으면 알아서 nginx 설정까지 추가해준다. (우와! 똑똑하다!)

server {
  server_name miconblog.com;

  location / {
    proxy_pass http://127.0.0.1:8888;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header Host $http_host;
  }
	
  listen 443 ssl; # managed by Certbot
  ssl_certificate /etc/letsencrypt/live/miconblog.com/fullchain.pem; # managed by Certbot    
  ssl_certificate_key /etc/letsencrypt/live/miconblog.com/privkey.pem; # managed by Certbot    
  include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot    
  ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot}
}

server {
  if ($host = miconblog.com) {
    return 301 https://$host$request_uri;
  } # managed by Certbot
    
  server_name miconblog.com;
  listen 80;
  return 404; # managed by Certbot
}

이렇게 인증서를 설정하고 nginx 를 재시작하면 짜짠~ 하고 잘 될줄 알았다. ㅎㅎㅎ 재시작후 블로그를 접속하면 CSS가 제대로 로드되지 않고 다 깨진다. 거기에 더불어 어드민 페이지에 아예 접속을 못하는 문제가 더 치명적이다.

무엇이 문제일까?

요즘은 검색보다는 chatGPT를 이용해 검색하기 때문에 이와 관련된 질의를 던졌다.

SSL 설정이후에 워트프레스 사이트에 스타일 시트를 못가져오고 이런 에러를 내고 있어.
Mixed Content: The page at ‘https://miconblog.com/’ was loaded over HTTPS, but requested an insecure stylesheet ‘https://miconblog.com/wp-content/themes/sparkling/assets/css/bootstrap.min.css?ver=6.5.5’. This request has been blocked; the content must be served over HTTPS.

그래서 얻은 대답은 블로그 설정에 사이트 주소와 워드프레스 주소를 변경하라는 둥둥… 여러가지 답을 주지만 죄다 헛방.. 근본적인 이유를 다시 생각해봤다. 아무래도 어드민을 접속하지 못하는 이유와 CSS를 로드하지 못하는 문제는 같은 원인인것 같다. 즉, 서버 랜더링하는 루트페이지를 제외하고 나머지 모든 페이지 링크들이 모두 302 무한 리다이렉션 되면서 리소스를 가져오지 못하는게 진짜 문제인게 아닐까? 뭔가 설정이 잘못된것 같은데… 오래만에 서버 관리하려니 머리가 안돌아간다. 다시 GPT 에게 질문을 던졌다.

SSL 이 없던 워드프레스에 Let’s Encrpty 로 SSL 인증서를 발급받고, nginx 설정에 아래와 같이 추가됐는데, 그 이후부터 https://miconblog.com/wp-admin/ 이런 접근은 모두 302 에러가 반복되고 있어.
(… 설정을 추가해서 질문함 …)

앗!! 이번에는 제대로 답을 준것같다. 생각해보니 Certbot 이 만들어준 nginx 설정에 http로 접속 했을때 https 로 리다이렉션하게 되는데,.. 리다이랙션이 무한히 반복되는 느낌이다.

실제로 워드프레스 설정(wp-config.php)에도 아래와 같이 리버스 프록시를 썼을때 방어하는 내용이 있다. 즉, HTTP 접속을 HTTPS로 바꿀때 무한 루프에 빠지는 문제를 해결하기 리버스 프록시를 거치면서 설정한 헤더값을 본다는 내용이다.

// If we're behind a proxy server and using HTTPS, we need to alert WordPress of that fact
// see also http://codex.wordpress.org/Administration_Over_SSL#Using_a_Reverse_Proxy
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
	$_SERVER['HTTPS'] = 'on';
}

그런데, 나는 HTTP_X_FORWARDED_PROTO 헤더를 리버스 프록시로 설정하지 않았다. ㅎㄷㄷㄷ

location / {
    proxy_pass http://127.0.0.1:8888;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-Proto $scheme;
}

nginx 설정에 간단히 한줄을 추가했더니 뚜뚱! 해결!
드디어 나도 HTTPS로 서빙한다. 야호~~~!

도커 컴포즈(Docker Compse)로 워드프레스 블로그 만들기

오랜전부터 운영해오던 블로그가 한번씩 죽을때마다 백업을 신경쓰지 않아서 이미지들을 죄다 날리곤 했었다. 이 문제 때문에 검색을 좀 해봤더니 Docker 볼륨과 Git 을 통해 손쉽게 백업 문제를 해결하고 있었다. 왜 이 생각을 못했지? ㅎㅎㅎ 머리가 굳었어!

일단 옆지기 블로그(janeisyoung.com)를 깃헙 페이지에서 해방 시켜주기로 했다.

서버 구성

AWS EC2 인스턴스 2개를 이용해 하나는 Docker 호스트로 쓰고 있고 다른 하나는 데이터베이스 전용으로 쓰고 있다. 따라서 지금부터 쓰는 글은 데이터베이스가 원격지에 있다는 가정하에 쓰는 글이다.

데이터베이스 설정

원격 서버에 SSH 접근한 후 블로그용 데이터베이스를 생성하고 이 데이터 베이스만 접근 가능한 유저를 만들어준다.

// 루트 권한으로 MySQL 로그인
> mysql -u root -p

// DB 생성 
mysql> create database janeisyoung;

// 유저 생성
> create user janeisyoung@'172.31.%' identified by '<PASSWORD>';

// 권한 지정
> grant all privileges on janeisyoung.* to janeisyoung@'172.31.%' identified by '<PASSWORD>' with grant option;

// 권한 확인
> show grants for 'janeisyoung'@'172.31.%';

참고로 데이터베이스로 쓰고 있는 인스턴스는 AWS EC2 간에 172로 시작하는 내부 IP를 통해서만 접근하도록 제한된 상태다.

도커 컴포즈 설정

version: "3.3"
services:
  wordpress:
    image: wordpress:latest
    ports:
      - "8889:80"
    restart: always
    environment:
      WORDPRESS_DB_HOST: <DB_HOST_IP>:<DB_PORT>
      WORDPRESS_DB_USER: janeisyoung
      WORDPRESS_DB_PASSWORD: <PASSWORD>
      WORDPRESS_DB_NAME: janeisyoung
    volumes:
      - ./html:/var/www/html

도커 호스트 서버에 접속해서 docker-compose.yml 파일을 만들고 실행한다.

> mkdir janeisyoung
> cd janeisyoung
> docker-compose up -d

컨테이너가 제대로 만들어졌는지 확인해보자.

> docker ps -a
CONTAINER ID  IMAGE             COMMAND                 CREATED        STATUS        PORTS                 NAMES
5c204cc3f0a8  wordpress:latest  "docker-entrypoint.s…"  3 minutes ago  Up 3 minutes  0.0.0.0:8889->80/tcp  janeisyoung

Nginx 멀티 호스팅

도커에는 이미 여러 컨테이너 서비스가 올라가 있다. 마찬가지로 이번에 추가한 블로그도 그 중에 하나로 8889번 포트를 통해서 도커 호스트와 통신한다. 그리고 도커 호스트로 들어오는 모든 요청은 Nginx 를 통해서 각각의 컨테이너 서비스로 연결된다.

file: /etc/nginx/conf.d/janeisyoung.com.conf
---- 
server {
  listen 80;
  server_name janeisyoung.com;

  location / {
    proxy_pass http://127.0.0.1:8889;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header Host $http_host;
  }
}

이제 Nginx 를 재시작한다.

> sudo systemctl restart nginx.service

도커 볼륨을 이용한 데이터 백업

앞에서 설정한 컴포즈를 실행하면 연결 해둔 볼륨(html 폴더)이 생기는데 이 폴더는 컨테이너 안쪽의 워드프레스 루트 폴더와 연동된다. 따라서 업로드한 모든 이미지와 설치한 테마, 플러그인 등등 모든 파일이 이 곳에 저장된다.

이제 도커 컨테이너가 어떤 이유로 죽더라도 내가 쓴글은 안전하게 DB에 저장되고 업로드한 모든 이미지는 볼륨 폴더에 저장되어있으므로 언제든 똑같이 되살릴수있다. 올레~ 그리고 이 볼륨 폴더를 git 연동해서 GitHub에 주기적으로 push 하면 서버를 이전할때도 손쉽게 이전할수있다.

물론 위에서 설정한 컴포즈 설정은 도커 명령어 한줄로도 가능하다.

docker run -d --name wordpress -v $(pwd)/html:/var/www/html -e WORDPRESS_DB_HOST=<DB_HOST>:3306 -e WORDPRESS_DB_USER=<DB_NAME> -e WORDPRESS_DB_PASSWORD='<DB_PASS>' -p 8888:80 wordpress:php7.4

Optimization React rendering

랜더링 최적화는 어떻게 하나?

1차적으로 불필요한 랜더링을 먼저 막습니다. 불필요한 랜더링이란 화면에 반영되는 데이터가 동일하거나 다른 화면에 가려져 랜더링이 아예 필요 없는 경우를 말합니다.

Props로 넘겨지는 데이터가 동일 할때 React.memo 를 사용하는 방법은 두 가지가 있습니다.

1. 기본 탑재된 비교 알고리즘을 쓰는 경우

React.memo(컴포넌트)

기본 알고리즘을 쓰는 경우는 Props로 넘어오는 값들이 이뮤터블임을 전제로 합니다. 뮤터뷸 객체가 넘어오면 당연히 이 전략은 통하지 않습니다. 여기서 신경써야하는 것들이 바로 핸들러나 콜백 함수들인데, JS에서 함수는 뮤터뷸한 객체라서 useCallback 으로 일일이 감싸서 캐시(메모이제이)하지 않으면 효과가 없습니다.

그렇다고 항상 useCallback 쓸 필요도 없습니다. 캐시 비용도 비용이지만 매번 콜백 함수를 감싸는 귀차니즘도 만만치 않기 때문에 그 비용이 오히려 비효율일 경우가 많습니다. 이럴때는 비교 로직을 커스텀 하는게 더 효과적 입니다.

2. 비교 알고리즘을 커스텀 하는 경우

React.memo(컴포넌트, 비교로직함수)

비교로직을 커스텀 할경우엔 콜백함수를 useCallback 으로 넘기든 넘기지 않든 랜더링에 필요한 요소들만 선택적으로 찾아서 랜더링을 결정합니다. 따라서 메모된 값을 쓰고 싶을 경우엔 true, 새로 랜더링을 할 경우엔 false를 비교 함수에서 반환합니다.

최적화 우선순위

모든 컴포넌트를 다 useCallback 으로 감싸거나 React.memo로 감쌀 필요는 없습니다. 병목 지점을 먼저 찾고 필요한 곳 위주로 처리하면 됩니다.

예를들어 아래와 같은 구조에서 최상위 Root 컴포넌트가 랜더링되면 나머지 A~F 컴포넌트가 줄줄이 랜더링 됩니다. 이때 A가 한번 랜더링 되면 F 같은 리스트 아이템들은 수없이 많이 랜더링 됩니다.

  ---Root
      |--- A
           |-- B ( List )
               |-- F
               |-- ... N 개의 F 
               |-- F
           |-- C
      |-- D

즉, 위와 같은 구조에서 F 컴포넌트가 제일 먼저 최적화 대상이 됩니다.

극단적인 랜더링 최적화 (한번만 그리기)

가끔은 컴포넌트를 딱 한번만 그리는 경우도 있습니다. 예를 들면 고정형 네비게이션바 같은 경우엔 Props를 주입받지만 화면을 그리는 요소에 영향을 주지 않기 때문에 이런경우엔 극단적인 선택을 할수있습니다.

export default memo(NavigationBar, () => true);

클로저 변수와 함께 콜백 함수 메모하기

   1번
   const 핸들러함수 = useCallback(()=>{
         const currentItem = items[currentIndex];
      //  currentItem 처리
   },[currentIndex])

    2번 
    const 핸들러함수 = (currentItem) => {
         // currentItem 처리....
    }   

    <List>
        {for item in items 
            return <ListItem item={item} onPress={핸들러함수}>
         }
     <List>

1번처럼 useCallback 함수를 사용해 클로저 변수를 함께 패키징 할 경우 핸들러 함수는 currentIndex 값에 따라 매번 다른 함수가 만들어집니다.

그리고 이것을 <ListItem> 컴포넌트의 프로퍼티로 넘기기 때문에 <ListItem> 컴포넌트의 메모 전략은 매우 단순하게 가져갈 수 있습니다.

export default memo(ListItem);

물론 currentIndex 갯수 만큼 메모하는건 그만큼 약점입니다.

반대로 useCallback 을 쓰지 않을 경우에는 당연히 ListItem의 최적화 전략이 달라져야합니다.

export default memo(ListItem, (prev: Props, next: Props) => {
  return prev.language === next.language;
});

그리고 2번처럼 핸들러 함수에 넘겨받은 값을 그대로 같이 반환해야합니다.

function ListItem(props){
  const { item } = props;

  return( 
     <TouchableOpacity onPress={() => onPress(item)}>
       // 블라블라...
     </TouchableOpacity>
  )
}

주의! 최적화 전략은 케바케라서 상황에 따라 적절히 취사 선택하려면 잘 알아야한다. 암튼 간만에 회사 일 하다가 긴 PR을 올려서 그대로 블로그에 옮겨 왔음.