Github Actions Database Test 도입하기

dev_Hyun·2024년 1월 21일
0
post-thumbnail

들어가기 앞서

Rest docs가 지원하는 메소드를 사용해 API 테스트 코드를 작성하고, 테스트 수행 성공을 기반으로 Open API 3 규격으로 변환해 정적 문서를 제공 받습니다. 이를 Swagger-ui 도구를 통해 API 문서를 사용자에게 제공합니다.

로컬환경에서는 API 문서에 접근하는 것에 성공하였으나, 배포 환경에서도 접근 할 수 있도록 개선하는 것이 목표였습니다.

목표를 달성하기 위해 데이터베이스 연동이 필요한 테스트를 통과가 선행되야 했습니다. Runners 환경에서는 로컬에 존재하는 Test Database에 접근할 수 없으니 Github Actions Market에서 검색되는 비공식 플러그인 getong/mariadb-action@v1.1을 사용해 Runners 위에 MariaDB를 설치했습니다. 그러나 SQLSyntaxErrorException 에러가 발생하며, 테스트를 통과하지 못했습니다.

이번 포스팅에서는 궁극적으로 API 문서를 배포하기 위해 거쳐간 난관 중 데이터베이스 테스트를 수행하며 맞이한 문제 해결 과정을 다루어보겠습니다.


데이터베이스 연동 테스트 실패 해결 과정

1) Service Container 도입

먼저, Github Actions Flow에 도입한 getong/mariadb-action@v1.1 액션을 아래 세 가지 이유를 근거로 깃허브에서 공식적으로 제공하는 Service Container 으로 변경했습니다.

  1. Non-Official Market Action은 충분한 레퍼런스를 제공하지 않는다.
  2. 데이터베이스 설치에 필요한 설정이 Service Container에 비해 더 간편하지 않다.
  3. 해당 Action의 사용자가 충분히 많지 않아, 문제가 발생하였을 때 해결의 실마리를 찾기 어렵다.

Github Service Container는 Actions Workflow에서 애플리케이션을 테스트 및 운영할 때 사용하는 도구를 Docker 컨테이너로 제공합니다.

services:
      mariadb:
        image: mariadb:latest
        env:
          MARIADB_ROOT_PASSWORD: ${{ secrets.MARIADB_ROOT_PASSWORD }}
          MARIADB_DATABASE: '데이터베이스 이름'
          MARIADB_ROOT_HOST: "%"
          MARIADB_CHARACTER_SET_SERVER: 'utf8mb4'
          MARIADB_COLLATION_SERVER: 'utf8mb4_unicode_ci'
        ports:
          - 3306:3306
        options: --health-cmd="healthcheck.sh --connect --innodb_initialized" --health-interval=10s --health-timeout=5s --health-retries=3

위 설정은 해당 링크를 참고하여 작성했습니다.

  • image : 도커 허브의 mariadb 공식 이미지에서 지원하는 버전을 확인할 수 있습니다. 링크
  • env : 이미지를 시작할 때 docker 실행 명령줄에 하나 이상의 환경 변수를 전달하여 MariaDB 인스턴스의 구성을 조정할 수 있습니다. 조정할 수 있는 환경 변수의 종류는 다음 링크에서 확인할 수 있습니다. 링크
  • options : --health-cmd="healthcheck.sh --connect --innodb_initialized" --health-interval=10s --health-timeout=5s --health-retries=3 이 명령은 테스트 중에 데이터베이스에 연결할 수 있는지 확인하며, 지정된 명령이 실패할 경우 도커가 컨테이너를 자동으로 다시 시작합니다. MariaDB를 통한 테스트를 계속하기 전에 데이터베이스가 완전히 가동되고 실행 중인지 확인하기 위해 간단한 mysqladmin 핑을 실행합니다.

2) SQLSyntaxErrorException 에러 해결 과정

상세 로그 출력 설정

에러의 원인 중 하나로 출력된 SQLSyntaxErrorException은 SQL 문법 규칙을 위반했을 때 발생합니다. 그러나 로컬환경에서 문법 규칙으로 인해 에러가 발생한 케이스가 없으므로 다른 측면에서 원인을 찾기 시작했습니다.(추후 돌이켜보니 컴퓨터는 거짓말 하지 않았습니다. 사실상 문법이 잘못되었기 때문입니다.)

java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:132 오류 만으로는 정확한 에러의 원인을 찾기 어렵습니다. 특히 IDE의 도움을 받을 수 없는 환경에서는 더욱 그렇습니다. 따라서 조금 더 자세한 예외 메시지를 확인하기 위해 ./gradlew build 명령어 뒤에 -i 옵션을 주도록 변경했습니다.

변경 후, 다시 Job을 수행하면 정말 긴 로그를 확인할 수 있습니다. 수행되는 테스트의 개수가 많을 수록 스크롤의 압박이 있지만 덕분에 원인을 찾을 수 있었습니다.

Cannot resolve reference to bean 'sqlSessionTemplate' while setting bean property 'sqlSessionTemplate'; nested exception is
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSourceScriptDatabaseInitializer' defined in class path resource [org/springframework/boot/autoconfigure/sql/init/DataSourceInitializationConfiguration.class]: Invocation of init method failed; 
nested exception is org.springframework.jdbc.datasource.init.ScriptStatementFailedException: Failed to execute SQL script statement 
#1 of class path resource [data.sql]: INSERT INTO TUSERACCOUNT{이하 쿼리 생략}; nested exception is java.sql.SQLSyntaxErrorException: (conn=12) Table '{테스트용 데이터베이스}.TUSERACCOUNT' doesn't exist

에러 로그를 확인해본 결과 data.sql 으로 테스트에 필요한 초기 데이터를 삽입하는 과정에서 문제가 발생했습니다. 분명 init.sql 스크립트를 사용해 테스트 데이터베이스에 테이블 생성을 위한 DDL을 실행하였음에도 테스트 데이터베이스에 TUSERACCOUNT 테이블이 존재하지 않습니다.

테이블 명 대소문자 구분

테이블을 생성했음에도 데이터를 삽입할 때 테이블이 존재하지 않는다는 모순이 발생한 이유는 MariaDB 그리고 MySQL 데이터베이스의 운영체제에 따른 테이블 명의 대소문자를 구분되는 여부가 다르기 때문이었습니다.

설정 키워드는 lower_case_table_names으로 0인 경우 테이블 생성 및 조회 시 대소문자를 구분합니다. 1인 경우 모두 소문자로 인식합니다. 이때 리눅스 계열은 0, 윈도우 계열은 1이 기본값으로 데이터베이스가 설치됩니다.

개발 및 테스트를 수행한 로컬환경이 윈도우여서 테이블 명의 대소문자가 혼재되어도 전혀 문제가 발생된 적 없지만, Github Actions Runners 환경은 runs-on: 설정 값이 Ubuntu 환경이어서 대소문자 구분이 문제가 되었습니다.

mariadb Service Container env 설정 하위에 MARIADB_LOWER_CASE_TABLE_NAMES: '1'를 추가하여 대소문자 구분을 하지 않도록 의도하였으나 설정이 적용되지 않았습니다. 때문에 SQL 쿼리가 사용되는 모든 영역을 대상으로 테이블명을 대문자로 통일하였고, 성공적으로 모든 테스트가 통과하는 것을 확인했습니다.

3) socket fail to connect to host 해결 과정

Github Actions에서 데이터베이스 테스트를 수행한 이후, 실제 서비스에서 사용될 데이터베이스의 대소문자 구분을 수동 설정 하기 위해 접속을 시도하였습니다. 그러나, 다음과 같이 Cloud VM 위에 설치된 MariaDB에 외부접속 도구 DBeaver, Datagrip 등을 통해 접속에 실패했습니다. 불과 1-2주일 전에도 정상 연결되었는데도 말이죠.

데이터베이스 환경은 다음과 같습니다.

vm : 오라클 클라우드
os : Ubuntu 20.04
mariadb : 10.4 버전

※ 데이터베이스의 설정을 변경 후에는 반드시 flush 및 재시작을 수행해야 정상적으로 반영이 됨을 유의해야 합니다.

1) 공용 서브넷 설정 점검

먼저, 클라우드 플랫폼에 접속해 데이터베이스 연결에 필요한 포트에 외부접속 가능하도록 설정되어있는지 점검했고, 해당 포트에 대해 모든 주소에서 접근이 허용됨을 확인했습니다.

2) VM 포트 개방 점검

Virtual Machine 에서도 3306 포트가 열려 있음을 확인하기 위해 netstat -nap | grep LISTEN 명령어를 사용했습니다.

3) mariadb 50-server.cnf 설정 파일 점검

/etc/mysql/mariadb.conf.d 경로의
mariadb 50-server.cnf 설정 파일을 편집기로 열어 3306 포트와, 모든 주소가 허용되었는지 확인했습니다. 127.0.0.1 또한 주석 처리 되어 있어야 합니다.

port = 3306
bind-address = 0.0.0.0

이미지 출처 : 링크

4) user 설정 점검

Github Service Container으로 생성한 MariaDB에 root 계정으로 접속할 것이기에 별도의 사용자는 생성하지 않고, root 계정으로 내부(localhost)/외부(%)에서 접속이 가능하도록 Host 설정이 올바르게 되어있는지 확인했습니다.

5) 해결 - iptables 세팅 재설정

문제가 되었던 것은 iptables 세팅이었습니다. 추가해주었던 인바운드 규칙이 모두 초기화 되어 있었습니다.

재시작이 필요한 패키지가 존재할 경우나 보안업데이트, 커널 업데이트가 필요할 때 *** System restart required *** 메시지가 출력되는데, 만약 iptables 설정을 별도의 파일로 저장하고 불러오는 과정이 없다면 restart에 의해 설정이 모두 초기화 된것이 원인이었습니다.

sudo iptables -I INPUT 5 -i {네트워크 인터페이스 명} -p tcp --dport 3306 -m state --state NEW,ESTABLISHED -j ACCEPT

네트워크 인터페이스 명은 자신이 사용하고 있는 환경에 따라 다르기 때문에 ifconfig 명령어를 통해 확인해 명시해야 합니다.

설정 후 데이터베이스 연동을 시도한 결과 정상적으로 연결되었습니다.

profile
공룡, 다람쥐 그리고 돌고래!

0개의 댓글