오늘 있었던 일이다.
그간 회사에선 AWS를 써왔는데, 특수한 사정이 있어 다른 기관의 클라우드를 써야 했다.
기존 AWS에서 쓰던 DB를 그 클라우드의 DB로 마이그레이션하고 이제 그 바뀐 DB로 connection 설정값을 바꿔서 사용하려는데,

connect ETIMEDOUT ...?
생전 처음 보는 에러.
행여 내가 password라도 잘못 적었나 싶어 기입한 설정값을 몇 번이고 뜯어봤다. 근데 틀린 건 없었다. 구글에 검색해보니 서버쪽 문제라고 한다. 서버쪽 방화벽에 막히거나 하는 문제라고..
..AWS RDS는 connection에 설정값만 써두고 연결 시도하면 문제 없었는데..ㅠ
사수님께 SOS를 치니 나더러 터널링을 시도해보라고 하셨다.

터널링이요..? 그게 뭔진 모르지만 일단 해보겠다고 하고 바로 구글링을 했다.

이놈의 ppak대가리... 터널...링...이... 당최... 뭔지 감을 잡을 수 없었다.
ssh 명령어도 제대로 못 써서 결국 사수님이 예시를 써주시고 나서야 비로소 감을 잡을 수 있었다. 결국 원격 서버와 db 간의 터널을 생성해주고 그 ssh 서버로 db 연결을 해주고 나서야 원하는 결과를 얻을 수 있었다.
알고보니 별 거 아니었던... 진짜 왜 삽질했지 나. 암튼 오늘이 가기 전에 배운 걸 정리해두자..
터널링(tunneling)이란
간단히 얘기하면 송신자와 수신자 간의 파이프(터널)를 만드는 것이다. 이 파이프를 통해서만 수신자에게 데이터를 보낼 수 있게 되는 것이다.
이 터널링이 필요한 경우가 있는데,
외부에서 특정 호스트에 접근할 수 없을 때(방화벽 제한 등의 이유로)
목적지가 아닌, 목적지와 터널을 구성하고 있는 호스트에 연결하여 우회적으로 접근하는 것이다.
그리고 그 터널링의 수단이 바로 SSH이다.
SSH 터널링 (SSH 포트 포워딩)
SSH 터널링은 크게 두 가지로 나뉘는데, SSH 연결을 수립하는 주체가 누구냐에 따라 구분된다.
가장 간단히 생각할 수 있는 방법. 로컬(즉, 내 컴퓨터)에서 원격 서버로 SSH 연결을 시도하는 것이다.
2) Remote port forwarding
1과 반대다. 이건 원격 서버에서 로컬 쪽으로 SSH 연결을 하는 것이다. 2의 장점은 원격 서버 쪽에서 22번 포트를 열어주고 말고의 문제를 생각할 필요가 없다는 것이다.
2의 예시를 들고오긴 귀찮아서 그냥 1로 쭉 예시를 들겠다.
터널링 예시
대충 아래와 같이 예시 구조를 그려볼 수 있겠다.
1) Client에서 Server A에 접근이 필요하지만 -> 방화벽에 막혀서 접근 불가
2) Server A와 같은 사설망에 있는 SSH Server 간에 터널 생성 (터널링)
3) Client는 Server A로 보낼 요청을 SSH Server로 하면 된다

이 터널링을 많이 쓰는 대다수의 경우가 DB 접속 때문일 것이다. DB는 보안이 정말 중요해서 같은 사설망에 있는 서버만 접속할 수 있게끔 해놓으니까.
SSH 터널링을 해보자.
로컬 -(SSH)-> EC2 -(터널)-> DB(RDS)
로컬에서 다이렉트로 DB에 접근하는 게 아닌 원격 서버와 DB 간에 터널링을 해준 후 로컬에서 원격 서버로 접근해서 DB에 연결하는 걸 해보겠다.
됬으니 고.
ssh 터널링을 하는 기본 스크립트는 다음과 같다.
$ ssh -L {local_port}:{target_host}:{target_port} -N {remote_user}@{remote_host}일단 저기 쓰인 옵션부터 좀 설명해 보자면,
우선 -L은 local port forwarding 을 하기 위한 옵션이다.
-N은 단순히 port forwarding만 해줄 뿐, 원격 커맨드를 실행하지 않는다. 이 옵션을 주고 실행하면 실제로 터미널에 아무것도 뜨지 않는다. 그래서 된건지 만건지... 엄청 긴가민가했었다.
저 둘 말고도 쓸 수 있는 SSH 옵션은 더 있지만 일단 저 두 개만 썼다.
$ ssh -i "serverkey.pem" -L 8032:192.22.5.311:3306 -N root@132.114.109.20
1) 내가 접근하고 싶은 db(또는 target host)의 host가 192.22.5.311이고 port는 3306
2) 대신 SSH 연결을 할 원격 host가 132.114.109.20, 접속명은 root, 접속 시 인증을 위해 "serverkey.pem"키를 사용했고,
3) 로컬에선 8032 포트로 원격 호스트 132.114.109.20에 포트 포워딩하여 db(target host)에 접근할 수 있게 되는 것이다.
db연결이 필요한 프로젝트에서 위의 정보를 바탕으로 connection을 수립한다.
// database.config.ts
import { TypeOrmModuleOptions } from '@nestjs/typeorm/dist';
export const DatabaseConfig: TypeOrmModuleOptions = {
type: 'mysql',
host: '127.0.0.1',
port: 8032,
username: (--db username--),
password: (--db password--),
database: (--db database--),
synchronize: false,
logging: true,
entities: [...(--db entities)...],
timezone: 'Z'
};
// app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { DatabaseConfig } from './config/databaseConfig';
@Module({
imports: [
...
TypeOrmModule.forRoot(DatabaseConfig),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
보다시피 host와 port가 본래 db의 host와 port가 아닌, 로컬 host와 포트 포워딩을 해준 로컬 포트를 적어줬음을 알 수 있다.
허허

참... 알고나면 간단한 건데 처음 해보는 거라 삽질은 어쩔수 없었나 보다. 사수님이 터널링은 백엔드가 반드시 알아야 되는 거라고 말씀해주셨다. 네... 열심히 공부할게요... 예..