Docker 환경에서 Selenium으로 웹 크롤링을 해야 할 일이 있었다. 로컬 환경에서는 따로 chromium을 다운받지 않고도 실행이 잘 되었지만, Docker 환경에서 돌려보니 크롬을 인식하지 못해서 WebDriver가 초기화되지 않는 문제가 발생했다. 그 과정을 어떻게 해결했는지 정리해보려 한다!
참고로 내가 마주한 에러는 다음과 같았다.
Error creating bean with name 'webDriver' defined in class path resource
# Chromium 설치
RUN apk add --no-cache \
gcompat glib nss libxcb libgcc \
chromium \
chromium-chromedriver
Dockerfile에서 Chromium을 설치해야 한다. A1pine Linux 환경에서는 apk를 통해 설치할 수 있다.
| 패키지 | 역할 | 중요도 |
|---|---|---|
gcompat | Alpine Linux와 GNU C 라이브러리 호환성 제공 | 필수 |
glib | Chromium 엔진 프로세스/메모리 관리 | 필수 |
nss | HTTPS/SSL 암호화 처리 | HTTPS 링크 접근에 필수 |
libxcb | X11 클라이언트 라이브러리 | 헤드리스 모드에서 렌더링에 필수 |
libgcc | GCC 런타임 라이브러리 | C++ 예외처리 및 기본 런타임 기능 |
chromium | 브라우저 | 필수 |
chromium-chromedriver | Selenium과 Chromium을 연결하는 드라이버 | 필수 |
여기서 사용한 패키지 외에도 자신이 필요한 패키지를 같이 다운받아서 사용하면 된다.
@Configuration
public class SeleniumConfig {
@Bean
public WebDriver webDriver() {
// ChromeDriver 경로 설정 (선택)
String chromeDriverPath = "/usr/bin/chromedriver";
System.setProperty("webdriver.chrome.driver", chromeDriverPath);
// Chromium 바이너리 경로 (필수)
ChromeOptions options = new ChromeOptions();
options.setBinary("/usr/bin/chromium-browser");
// 헤드리스 모드
options.addArguments("--headless=new");
options.addArguments("--no-sandbox");
options.addArguments("--disable-dev-shm-usage");
options.addArguments("--disable-gpu");
return new ChromeDriver(options);
}
}
설치한 Chromium을 Spring Boot 환경에서 사용하기 위해 WebDriverConfig를 작성해준다. 이때 Chromium 브라우저를 찾기 위해 chromium-browser의 위치를 확인해서 setBinary()에 넣어줘야 한다.
| 옵션 | 설명 | 중요도 |
|---|---|---|
--headless=new | GUI 없이 실행하는 헤드리스 모드 | 필수 |
--no-sandbox | 샌드박스 보안 기능 비활성화 | Docker 컨테이너에 필수 |
--disable-dev-shm-usage | /dev/shm 공유 메모리 비활성화 | 메모리 부족 방지 |
--disable-gpu | GPU 하드웨어 가속 비활성화 | 안정성 향상 |
@Bean
@Scope("prototype")
public WebDriver webDriver() {
// 로직 구현
}
기본적으로 Bean Scope는 singleton으로 설정이 되어 있다. Scope를 singleton으로 설정하면 애플리케이션 전체에서 하나의 WebDriver 인스턴스를 공유하게 된다.
하지만 Scope를 prototype으로 설정하면 getBean()을 호출할 때마다 새로운 WebDriver 인스턴스를 생성한다. prototype을 설정된 Bean은 Spring이 생성만 담당하고, 소멸은 개발자가 직접 관리해야 한다. 따라서 반드시 수동으로 quit()을 호출해야 한다.
private final ApplicationContext context;
public void useWebDriver() {
WebDriver webDriver = context.getBean(WebDriver.class);
try {
// 로직 구현
} finally {
webDriver.quit();
}
}
스케쥴러가 돌 때마다 WebDriver 인스턴스를 생성하고 해제할 수 있도록 한다. 이때 WebDriver는 @Autowired로 직접 주입하면 안 되고, context.getBean(WebDriver.class)로 매번 새로운 인스턴스를 생성해야 한다.
WebDriver 사용이 끝나면 webDriver.quit()을 통해 인스턴스를 해제한다. 이렇게 하면 WebDriver를 사용할 때만 메모리가 사용되고, quit()이 호출되면 사용하던 메모리를 해제하기 때문에 메모리 최적화에 도움이 된다.
크롬 드라이버, 셀레니움, 도커 환경 연동
[Spring] 빈 스코프란? (Singleton, Prototype Scope)