Springboot 터널링(스프링 부트 터널링, Spring boot ssh, 스프링 부트 ssh)
Springboot에서 터널링을 활용하여 데이터 베이스에 접속해야 되는 기능을 만드는 것은 어렵지 않습니다.
터널링을 하여 주는 클래스를 생성한 뒤에 SSH를 통해서 원하는 포트로 연결을 해 준 다음에 데이터베이스를 사용하면 됩니다.
아래와 같은 터널링용 클래스가 있다고 가정하여 봅니다.
데이터베이스는 몽고DB에 접속하는 상황 입니다.
* 터널링용 클래스 예제
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
public class SshTunneling {
private static JSch jsch = new JSch();
private String url = "원격주소";
private String username = "아이디";
private String password = "비밀번호";
private int port = 22; //ssh포트
private int lport = 26016; //원격 접속 후 가상으로 포워딩할 포트
private int rport = 27017; //실제 사용할 데이터베이스 포트
private Session session;
public SshTunneling() {
try {
session = jsch.getSession(username, url, port);
session.setPassword(password);
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.connect();
session.setPortForwardingL(lport, "127.0.0.1", rport);
} catch (Exception e) {
}
}
public void shutdown() throws Exception {
if (session != null && session.isConnected()) {
session.disconnect();
}
}
}
이러한 클래스를 @Bean 에노테이션을 활용하거나 @Component 에노테이션등을 활용하여 객체를 빈으로 생성하여 주고 데이터베이스에 연결하면 됩니다.
* 물론! 포트 포워딩이 된 상태 이므로 데이터베이스에 연결 할 때는 가상화 포트를 사용하여야 합니다!
데이터베이스가 몽고DB, Maria, MySql, Ms-sql등 어떠한 데이터베이스던지 간에 상관 없습니다.
그런데 잘 동작중인 프로젝트가 아래와 같은 연결이 되지 않는 오류를 만날 때가 있습니다.
1. 프로젝트를 빌드(build) 할 때 .
2. 서비스의 생성자나 @PostConstruct를 통해 붙여진 메소드에서 데이터 베이스에 바로 접속을 시도하다 할 때.
데이터 베이스 커넥션이 안된다는 단순한 내용입니다.
해당 문제는 스프링 부트의 빈 객체의 생성 순서에 따른 문제 입니다.
@Service나 @Controller로 작성된 클래스는 @Bean과 @Component로 작성된 클래스보다 먼저 생성되게 됩니다.
이러한 현상 때문에 터널링용으로 만든 클래스가 먼저 동작하지 않았는데 데이터베이스에 접속을 시도하다 오류가 발생하게 되는 것 입니다.
이러한 경우를 방지하기 위해서는 터널링용 클래스가 가장 먼저 동작하도록 해 주어야 합니다.
위 SSH 접속용 클래스를 먼저 간단하게 수정 합니다.
* 터널링용 클래스 예제
import java.util.function.Consumer;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
public class SshTunneling {
private static JSch jsch = new JSch();
private String url = "원격주소";
private String username = "아이디";
private String password = "비밀번호";
private int port = 22; //ssh포트
private int lport = 26016; //원격 접속 후 가상으로 포워딩할 포트
private int rport = 27017; //실제 사용할 데이터베이스 포트
private Session session;
public SshTunneling init(Consumer<Boolean> arg) {
try {
session = jsch.getSession(username, url, port);
session.setPassword(password);
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.connect();
session.setPortForwardingL(lport, "127.0.0.1", rport);
arg.accept(true);
} catch (Exception e) {
arg.accept(false);
}
return this;
}
public void shutdown() throws Exception {
if (session != null && session.isConnected()) {
session.disconnect();
}
}
}
init 메소드를 호출하면 터널링 기능이 동작하도록 변경 하였습니다.
포워딩이 성공적으로 이루어지면 컨슈머 클래스에 true, 실패하면 false를 반환하게 하였습니다.
나머지 내용은 크게 어렵지가 않습니다.
이를 호출하는 클래스는 스프링부트의 main메소드가 존재하는 곳 입니다.
* main메소드가 존재하는 클래스 예제
import javax.annotation.PreDestroy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.Banner.Mode;
import 터널링패키지.SshTunneling;
@SpringBootApplication
public class SpringBootApplication {
private static SshTunneling tunnel;
public SpringBootApplication() {
tunnel = new SshTunneling().init( res->{
if(!res) {
System.out.println("포트포워딩 실패, 프로그램을 종료 합니다.");
System.exit(0);
}
});
}
public static void main(String[] args) {
SpringApplication app = new SpringApplication(SpringBootApplication.class);
app.run(args);
}
@PreDestroy
public void end() {
try {
tunnel.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
}
}
main메소드가 존재하는 클래스에서 생성자(constructor)로 터널링 클래스를 동작하게 하였습니다.
데이터베이스가 연결이 되지 않으면 큰 문제이므로 프로그램이 종료 되도록 하였습니다.
그리고 @PreDestroy 에노테이션을 통해서 터널링한 커넥션을 끊어주도록 하였습니다.
초기화 블럭을 사용하여도 상관은 없습니다.
매우 단순한 방법이지만 효과(?)는 아주 확실합니다.
터널링(SSH)을 통해서 프로젝트가 구성 된 다면 이처럼 터널링용 클래스가 가장 먼저 동작 하도록 해 주는 것에 주의하여야 하겠습니다!
궁금한점 또는 틀린점 및 수정이 필요한 부분은 언제든 연락주세요!