DevOps

반복적인 일을 자동화하자!! .1부 (시스템 점검 자동화)

백엔드 유성 2023. 4. 1. 16:33

회사에서는 출근 후, 퇴근 전 매일 하는일이 있다.

바로 시스템 점검.. 우리 시스템이 잘 운영되고 있는지 모든 api를 점검한다.

 

지금까지 시스템을 점검하는 방법은 모두 Postman으로 한건한건 찔러보는 것이다.

이중화 되어있는 서버를 모두 찌르기만해도 20분이 훌쩍... 보고서까지 작성하면 30분이 증발한다..

 

api서버를 모두 찌르기만 하는것도 아니다..

api서버에서는 데이터를 잘 가져와도, api 뒤쪽에 있는 데이터 생성 서버가 3시간 전에 죽었으면

api서버는 3시간 전의 데이터를 잘(code:200) 가져온다... 또한 데이터는 .svg 파일이다. 직접 사진을 열어 사진에 적힌 시간을 봐야한다.

 

매일 하는 반복적인 일에 30분이나 시간을 쓰는 것도 아깝고, 모든 api를 찌는것도 귀찮기 때문에

일일점검 자동화를 만들게 되었다!! (사실.. 후자의 이유가 크다... 매우...)

 

만들기 전에 생각해봐야 할것들이 있다.

  1. 테스트 설정이 간단해야 한다.
    • PROD, TB 서버의 IP가 바뀌거나 담당자가 내가 아닌 다른사람으로 바뀌었을 때도 재사용 했으면 좋겠다.
  2. 코드를 수정하기 쉬워야 한다.
    • 입사하기 전에는 전혀 몰랐으나, 내 코드는 나만 보는게 아니다. 시간이 지나면 내 후임자가 내 코드를 볼것이고 더 지나면 후임자의 후임자가 내 코드를 볼것이다. 그 사람들의 업무 적응속도는 지금의 내가 정한다고 생각한다.
  3. 외부 라이브러리를 사용하면 안된다.
    • 스프링 스타터 같은 외부 라이브러리를 사용하면 개발은 쉬워지나.. 내부망에서 테스트를 진행해야 하므로 라이브러리를 다운받고 관리하기가 너무 귀찮다..
      저번에 메모리 문제때문에 라이브러리 2개를 업데이트하는데 하루 종일을 써서 그런것은 아니다... 사실 맞다..
  4. 테스트 실행시간이 빨랐으면 좋겠다.
    • 직접 만들어서 돌려보니 데이터 파일 크기가 커서 30초나 걸린다.. 이건 큰 문제다...
      10초 이내로 줄여 사내 카페에서 다른사람들보다 줄을 빨리 서야한다. 요즘 줄이 길다..

 

 

고려할 것들은 위의 내용이고

개발을 시작하자. jdk는 회사 시스템에 사용중인 1.8버전을 사용한다.

블로그 글은 편의상 jdk17을 사용한다. (일일점검에 돌고있는 코드는 내부망 PC에 있으므로 공유가 불가능하다..)

 

일단 Http 요청을 서버에 보내야 하므로 http 요청 객체를 만들어보자.

객체는 이름은 ConnectionFactory로 했다.

 

역시 개발자는 글보다 코드가 편하니

public class ConnectionFactory {

    private HttpURLConnection conn;

    public HttpURLConnection getConn() {
        return conn;
    }

    public ConnectionFactory(String spec) {
        // spec = 파라미터가 포함된 full url
        URL url = createURL(spec);
        this.conn = openConnection(url);
    }

    private HttpURLConnection openConnection(URL url) {
        try {
            return (HttpURLConnection) url.openConnection();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private URL createURL(String spec) {
        try {
            return new URL(spec);
        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        }
    }


    public void start() {
        try {
            conn.connect();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void stop() {
        if (conn != null)
            conn.disconnect();
    }
}

코드는 간단하다. (그러나 Checked Exception 때문에 좀 더럽다.)

connection을 책임지는 ConnectionFactory 객체

connection 객체를 저장하고 있으며, 연결(start) 및 종료(stop) 메소드를 가지고 있다.

connection 연결 중 발생하는 에러는 Runtime으로 다 떤졌다. 어디서 문제가 발생했는지 알수있게 나중에 log 몇개 찍어야겠다.

 

일단 연결이 잘 되는지 찍어보자.

 

Main.java

public class Main {

    public static void main(String[] args) throws IOException {
        ConnectionFactory factory = new ConnectionFactory("https://www.google.com/");
        factory.start();

        HttpURLConnection conn = factory.getConn();
        int responseCode = conn.getResponseCode();
        System.out.println("responseCode = " + responseCode);
    }
}

status code가 200이 뜨는것을 보니 잘 된다.

(만약 실행중 멈추거나 하면 conn의 timeout등 설정들을 만져보자. 여러가지가 있었다.)

 

이제 config 파일을 만들어보자.

public enum UrlPath {

    NAVER("https://www.naver.com/", "네이버"),
    GOOGLE("https://www.google.com/", "구글"),
    TSTORY("https://www.tistory.com/", "티스토리");

    private final String url;
    private final String name;

    UrlPath(String url, String name) {
        this.url = url;
        this.name = name;
    }

    public String getUrl() {
        return url;
    }

    public String getName() {
        return name;
    }
}

나중에 property 파일(외부 설정 파일)을 만들어야 해서 해당 enum을 사용하지는 않겠지만 간단하게 짜보았다.

 

이제 테스트를 진행해보자.

public class Main {
    public static void main(String[] args) throws IOException {

        boolean testStatus = true;

        for (UrlPath urlPath : UrlPath.values()) {
            // init
            ConnectionFactory factory = new ConnectionFactory(urlPath.getUrl());
            factory.start();

            String testName = urlPath.getName();
            // test
            HttpURLConnection conn = factory.getConn();
            boolean result = validation(conn, testName);

            testStatus = testStatus && result;

            // flush
            factory.stop();
        }

        if (testStatus) {
            System.out.println("테스트 성공!");
        } else {
            System.out.println("테스트 실패");
        }
    }

    private static boolean validation(HttpURLConnection conn, String testName) throws IOException {
        int statusCode = conn.getResponseCode();
        if (statusCode != 200) {
            // TODO 테스트 실패 로그 출력
            return false;
        }
        System.out.println(testName + " 테스트 성공!");
        return true;
    }
}

검증 로직은 status 코드가 200이면 성공, status 코드가 200이 아니면 실패로 잡았다.

시스템에 따라 validation 로직은 바뀔 수 있다.

테스트도 잘 성공하였다.

블로그를 위해 만든 코드는 내부망 PC에서 만든 시스템 점검 모듈보다 1/10정도로 꽤나 심플하지만

중요한 개념들은 이게 전부다 ㅎㅎ;;

 

2부로 끝날지 3부로 끝날지 모르겠지만 다음 작업은 아래 4개가 남아있다.

  1. conn 객체에서 Response Content 를 String 타입으로 가져오기
  2. conn 객체에서 Response Content 다운받기(이미지 및 압축파일 등)
  3. 외부 파일을 설정파일로 읽을 수 있는 PropertyManager 만들기
  4. 비동기 처리로 테스트 시간 줄이기

 

+ 추가

테스트 진행할때 PostMan이나 크롬으로 찔렀을땐 성공하나 위 코드로 찔렀을때 400 실패하는 경우가 있다.

그땐 url에 한글이 있어서 그런 경우일 수 있으니 "URLEncoder.encode("한글")" 메소드를 사용해보자.

 

또한 하나의 라이브러리에 얽매일 필요는 없다.

단. 추후에 어떤 변경사항이 있을지 모르니 너무 라이브러리나 프레임워크에 의존하지만 않으면 좋겠다.