-
Notifications
You must be signed in to change notification settings - Fork 1
[BE] ssh터널링으로 db연결
로컬 → 웹서버 → db서버
db서버는 private subnet으로 외부에서 접속이 불가능하기때문에 로컬에서 db서버에 접속을 하기 위해서는 ssh터널링을 통해 접속해야 한다.
- SSH 터널링을 사용하여 로컬 호스트를 통해 원격 서버의 데이터베이스에 연결하면, 로컬 시스템이 마치 원격 서버처럼 작동하는 것처럼 취급됩니다.
- 이 방법은 개발 환경에서 원격 데이터베이스에 접근할 때 보안과 편의성을 동시에 제공하며, 데이터베이스 연결을 단순화합니다.
//ssh-server에 들어가서 원격 서버 명령어가 실행이 가능합니다.
//주의할 점은 3306포트를 db서버에서 이미 사용중이라 앞 뒤를 똑같이 3306을 사용하게 될 경우 충돌이 발생 할 수 있습니다.
ssh -L 3307:remote-db-host:3306 user@ssh-server
//ssh-server에 터널링만 진행하고 원격 서버 명령어를 실행하지 않도록 해서 아무 반응이 없습니다.
ssh -L 3307:remote-db-host:3306 -N user@ssh-server
//이제 터널링을 했으면 접속을 해봅시다.
//mysql -h (로컬 호스트) -P (접속 포트) -u (사용자 이름) -p
//SSH 터널링을 통해 로컬에서 포트 3307으로 원격 MySQL 서버에 접근하고, corinee 사용자로 로그인하려는 명령어입니다.
mysql -h 127.0.0.1 -P 3307 -u corinee -p
const connection = mysql.createConnection({
host: '127.0.0.1', // 로컬 호스트 사용
port: 3307, // 로컬 포트
user: 'db_user',
password: 'db_password',
database: 'db_name'
});
- localhost:3307는 ssh-server를 터널링해서 db server에 연결한다.
SSH 터널링을 사용하여 로컬 머신에서 원격 네트워크에 있는 MySQL 서버에 접근할 수 있도록 설정 후 private db에 연결할 수 있다.
하지만 매번 개발때마다 여러 터미널을 열고 수동으로 터널링을 하기는 귀찮다..
그래서 node에서 제공하는 여러 모듈을 사용하여 자동화를 해보기로 했다.
처음에는 ssh2모듈을 사용하여 시작해 인터넷과 gpt를 찾아보며 여러 레퍼런스를 시도하였지만 알 수 없는 에러로인해 계속 실패하였다.
터널링하는 포트가 3306으로 mysql 포트와 겹쳐서 충돌이 난다는 글을 보고 3307로 바꾼뒤 연결해봤지만 여전히 실패…
@Module({
imports: [
AuthModule,
TypeOrmModule.forRootAsync({
useFactory: async () => {
return await getTypeOrmConfig(); // SSH 터널링이 완료된 후 TypeORM 설정 로드
},
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
다음은 터널링 되기전에 db연결을 먼저시도하는 문제인 줄 알고 await로 먼저 터널링 후 db연결을 시도했다. → 실패
gpt o1-preview한테 물어봤다.
현재 사용하신
ssh2
라이브러리의forwardOut
메서드는 단일 연결에 대한 포워딩을 처리합니다. 하지만ssh -L
명령어처럼 로컬 포트를 열어 여러 연결을 포워딩하려면,net
모듈과 함께 사용하거나, 더 간단하게는tunnel-ssh
라이브러리를 사용하는 것이 좋습니다.
바로 tunnel-ssh 라이브러리를 사용했지만 여전히 발생하는 문제들
구글링 레퍼런스를 보며 구현을 해봤지만 tunnul이라는 함수를 못불러오는 문제가 계속 발생했다.
commonJS 문제인지 뭔지 계속 방법을 찾아보았지만 실패..
거지같은 tunnel is not a function
을 해결하지 못했다.
그러던 중 tunnel 라이브러리를 다운그레이드해서 설치해서 되었다는 글을 보고 공식문서를 찾아보기로 했다.
최신버젼에서는 createTunnel로 변경되고 구조가 많이 변경된 것을 볼 수 있었다.
결국 공식문서 레퍼런스를 보고 해결 !
import {createTunnel} from 'tunnel-ssh';
const sshOptions = {
host: '192.168.100.100',
port: 22,
username: 'frylock',
password: 'nodejsrules'
};
function mySimpleTunnel(sshOptions, port, autoClose = true){
let forwardOptions = {
srcAddr:'127.0.0.1',
srcPort:port,
dstAddr:'127.0.0.1',
dstPort:port
}
let tunnelOptions = {
autoClose:autoClose
}
let serverOptions = {
port: port
}
return createTunnel(tunnelOptions, serverOptions, sshOptions, forwardOptions);
}
await mySimpleTunnel(sshOptions, 27017);
https://www.npmjs.com/package/tunnel-ssh
하지만 터널링은 로컬 개발환경에서만 사용하고 배포서버에서는 같은 서브넷인 db에 바로 연결해야 한다.
const env = process.env.NODE_ENV || 'development';
export default async function getTypeOrmConfig(): Promise<TypeOrmModuleOptions> {
if(env === 'development') await setupSshTunnel();
return {
type: process.env.DB_TYPE as 'mysql',
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
entities: [__dirname + '/../**/*.entity.{js,ts}'],
synchronize: Boolean(process.env.DB_SYNCHRONIZE),
};
}
Node_ENV 환경설정에 따라 분기를 나누도록 했다.
이제 배포시 db에 제대로 연결하는지만 확인하면 해결…
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import { config } from 'dotenv';
import { createTunnel } from 'tunnel-ssh';
config();
//여기서 process.env.NODE_ENV는 실행할 때 NODE_ENV에 대한 정보를 넣어주지 않으면 NULL값입니다.
//그래서 default값으로 development를 넣어줬습니다.
const env = process.env.NODE_ENV || 'development';
config();
const env = process.env.NODE_ENV || 'development';
export default async function getTypeOrmConfig(): Promise<TypeOrmModuleOptions> {
if(env === 'development') await setupSshTunnel();
return {
type: process.env.DB_TYPE as 'mysql',
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
entities: [__dirname + '/../**/*.entity.{js,ts}'],
synchronize: Boolean(process.env.DB_SYNCHRONIZE),
};
}
async function setupSshTunnel(): Promise<void> {
return new Promise((resolve, reject) => {
const sshOptions = {
host: process.env.SSH_HOST,
port: Number(process.env.SSH_PORT),
username: process.env.SSH_USER,
password: process.env.SSH_PASSWORD,
};
tunnel(sshOptions, process.env.DB_PORT)
.then(() => {
resolve();
})
.catch((error) => {
console.error('SSH 터널링 설정 중 오류 발생:', error);
reject(error);
});
});
}
function tunnel(sshOptions, port, autoClose = true): Promise<void> {
return new Promise((resolve, reject) => {
const forwardOptions = {
srcAddr: process.env.DB_HOST,
srcPort: port,
dstAddr: process.env.SSH_DB_HOST, // 원격 DB 서버 IP
dstPort: Number(process.env.SSH_DB_TUNNUL_PORT), // 원격 DB 포트
};
const tunnelOptions = {
autoClose: autoClose,
};
const serverOptions = {
port: port,
};
createTunnel(tunnelOptions, serverOptions, sshOptions, forwardOptions)
.then((server) => {
console.log('SSH 터널링이 성공적으로 설정되었습니다.');
resolve();
})
.catch((error) => {
console.error('터널 생성 중 오류 발생:', error);
reject(error);
});
});
}
- [FE] TailwindCSS @apply
- [FE] 캐러셀 구현
- [FE] 사이드 바 상태관리 도전기
- [FE] axios interceptor로 로그인 필요한 api 개선하기
- [FE] Tanstack Query API 최적화 도전기
- [FE] Tanstack Query로 구현하는 무한 스크롤 차트 도전기
- [FE] 차트 무한 스크롤링 최적화 도전기
- [FE] 차트 실시간 등락 구현 도전기
- [FE] 검색 구현 및 검색 API 호출 최적화 도전기
- [FE] 고차 컴포넌트를 활용한 인증 접근 제어
- [FE] 코드 스플릿팅으로 최적화 도전기
- [BE] Server 생성
- [BE] CI/CD
- [BE] GitAction 학습 정리
- [BE] ssh터널링으로 db연결
- [BE] 배포환경에서 DB 연결 및 테스트 완료
- [BE] https 적용
- [BE] upbit api 연결 및 SSE api
- [BE] SSE 구현
- [BE] SSE 에러
- [BE] redis 설치 및 연동
- [BE] 트랜잭션 락 구현과 최적화
- [BE] Oauth CORS
- [BE] QueryRunner 사용 시 발생한 문제점과 해결방안
- [BE] Git Action 학습 정리
- [BE] NestJS 학습 정리
- [BE] 로그인 기능 및 리프레시토큰
- [BE] 비회원 체험 기능
- [BE] Nginx 학습 정리
- [BE] Mixed Content와 HTTPS 보안 구현하기
- [BE] 매수/매도 로직 구현 및 개선 과정
- [BE] Queue, Load Balancing, Redis