Github action 배포 시 yml 암호화 및 암호화 키 환경변수 등록하기

백승호·2022년 6월 10일
3

무중단 배포를 적용한 이유

UT기간 중 이러한 피드백을 받았습니다.

사실 우리 팀원들은 알고 있었습니다. 이것이 우리가 업데이트한 내역을 서버에 반영하기 위해 운영 중이던 서버를 내리고 다시 jar파일을 올리는 과정에서 일어난 일이란 것을 말입니다..ㅎㅎ

오랜 시간 동안 멈춘 것은 아니었겠지만 유저가 불편하다는 피드백을 주었고, 우리 서비스에는 없지만 결제 작업이나 정말 중요한 문서작업을 하던 중 서버가 내려갔다면, 이는 추후에 정말 복잡하고 큰 이슈로 번질 우려가 있습니다.

그래서! 어떻게 하면 업데이트 한 것을 수월하게 배포하고, 유저는 사용하는 데에 불편함이 없을까 고민을 하다가 Github Action, S3, CodeDeploy, Nginx를 활용한 무중단 배포를 구현하기로 했습니다.
Jenkins와 같은 CI/CD 툴도 있었지만 git과 aws 환경에 더욱 자신이 있고 편하기도 하며, 참고할 레퍼런스가 많아서 짧은 기간 동안 수행하기엔 Github Action이 나을 것이라고 최종 판단했습니다.

문제 상황

우선 Github Action을 구현하기 위해 사용한 여러 과정이나 쉘 파일들은 따로 설명하지 않겠습니다. 지금은 참고한 자료만 올리고 추후에 적용 방법은 따로 올려볼 수 있도록 하겠습니다..!

다음과 같이 원본 프로젝트 develop 브랜치에 push 가 될 때 github action 이 동작하도록 설정했습니다.

부푼 마음으로 해당 브랜치로 첫 push를 해봤지만 결과는

application start에서 health check를 통과하지 못하고 실패했습니다.
어디에서 실패한 것인지 궁금해서 EC2 인스턴스에서 nohup.out 파일을 cat으로 열어보았습니다.

${cloud.aws.credentials.access-key}의 변수를 읽어와서 injection 하는 것에 실패했다고 합니다. s3 키를 못 읽어오니 당연히 amazonS3Config 클래스도 빈등록에 실패한 것입니다.

access-key와 scret-key를 깃헙 세팅으로 등록해줬는데 왜 못 읽어오지.. 라는 고민에 빠졌던 찰나, .gitignore 파일에 application.yml 환경설정 파일을 등록해놨던 것이 떠올랐습니다!
기존에 yml이 노출돼서 등록돼있던 S3가 권한이 막혔던 적이 있었기에 특히 신경써서 .gitignore 시키고 있었던 것이었습니다 ㅎㅎㅎ

해결

yml 파일을 암호화해서 올리고, 나중에 jar파일을 읽을 때 복호화해서 읽어내는 흐름을 생각했고 , 마침 jasypt 라는 암호화 라이브러리가 제공되는 것을 알았습니다.

우선 jasypt 라이브러리를 사용하기 위해선 build.gradle에 관련된 의존성을 추가해주고 yml 파일에 ${jasypt.encryptor.password} 정보를 설정해주었습니다.

그리고 나서 jasypt config 클래스를 작성해주어 프로젝트에 적용했습니다.

Jasypt config 클래스


@Configuration
public class JasyptConfig {

    @Value("${jasypt.encryptor.password}")
    private String encryptKey; //이 암호화 키로 정보를 암호화시키는 것

    @Bean("jasyptStringEncryptor")
    public StringEncryptor stringEncryptor(){

        PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
        SimpleStringPBEConfig config = new SimpleStringPBEConfig();

        config.setPassword(encryptKey);
        config.setPoolSize("1");
        config.setAlgorithm("PBEWithMD5AndDES");
        config.setStringOutputType("base64");
        config.setKeyObtentionIterations("1000");
        config.setProviderName("SunJCE");
        config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
        config.setIvGeneratorClassName("org.jasypt.iv.NoIvGenerator");
        //config를 바탕으로 암호화 진행
        encryptor.setConfig(config);

        //암호화 결과물 return
        return encryptor;
    }
}

이렇게 설정을 해주고, 암호화를 시키기 위해서 jasypt 암호화 사이트에 들어가서 진행해도 되지만 저는 jasypt 테스트 코드를 활용하였습니다. 아래 테스트 코드 처럼 암호화 하고싶은 내용과 암호화 키를 입력해주면 되는데, 암호화 키는 외부로 공개가 되면 안 되겠죠? 이는 조금 이따가 다루겠습니다.

Jasypt test 코드 -> 암호화한 문자열 반환


public class JasyptTest {

    @Test
    public void stringEncryptor(){
        String text="암호화하고 싶은 내용";

        PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
        SimpleStringPBEConfig config = new SimpleStringPBEConfig();
		//
        config.setPassword("암호화 키");
//        config.setPassword("");
        config.setPoolSize("1");
        config.setAlgorithm("PBEWithMD5AndDES");
        config.setStringOutputType("base64");
        config.setKeyObtentionIterations("1000");
        config.setProviderName("SunJCE");
        config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
        config.setIvGeneratorClassName("org.jasypt.iv.NoIvGenerator");
        //config를 바탕으로 암호화 진행
        encryptor.setConfig(config);

        String encryptText=encryptor.encrypt(text);
        System.out.println(encryptText);
        String decryptText= encryptor.decrypt(encryptText);
        System.out.println(decryptText);
        assertThat(text).isEqualTo(decryptText);
    }

}

결과물(yml 파일)


이런식으로 암호화 된 내용에 ENC()로 감싸주기만 하면 암호화 처리 끝입니다. 그럼 이렇게 하고 다시 develop 브랜치로 push하면 이제 되겠죠??

문제 상황 2 및 해결

환경변수 인식 실패

암호화 키가 테스트 코드나 yml 파일에 그대로 노출되면 안 되기 때문에 환경변수로 설정할려고 했습니다.

첫 번째 시도(인텔리제이 환경설정)

첫 번째로 시도한 방법은 인텔리제이에 환경변수로 설정하는 것이었습니다.

이렇게 설정 하고 로컬에서 동작 시 이상 없이 실행이 돼서 부푼 마음으로 gihub actoin을 실행시켜봤습니다. 그러나 결과는

JASYPT_PASSWORD 환경변수를 여전히 읽지 못하고 있었습니다.

곰곰히 생각해보니 인텔리제이는 코드를 작성해주는 툴일 뿐이지, 리눅스 우분투 환경에서는 빌드된 jar 파일을 읽어오는 것이라는 것을 깨달았습니다. 그럼 jar 파일이 배포되고 실행되는 환경은 우분투일테니 우분투에 환경설정을 해주면 되겠지??

두 번째 시도(우분투 환경설정)

https://www.leafcats.com/201
위 블로그를 참고하여 우분투에서 환경변수를 등록하는 방법을 적용했습니다.

  1. sudo vim /etc/bash.bashrc
  2. 하단에 "export JASYPT_PASSWORD=암호화 키" 추가
  3. source /etc/bash.bashrc (환경 변수로 적용하는 명령어)

이렇게 진행 후 env 명령어를 입력해 환경변수로 잘 등록됐는지도 확인했는데, OK! 잘 등록돼있습니다.

또 다시 부푼 마음으로 github action을 동작시켰습니다. 결과는!!!

또.. 똑같은 놈입니다 ㅜㅜ 로컬 pc에서 윈도우 환경변수 설정에서 path설정하여 테스트 해봤을 때는 잘 돼서 리눅스 환경에 마찬가지로 세팅해주면 될줄 알았는데, 아무래도 github action이 이런식으로 흘러가는게 아닌 것 같습니다.

세 번째 시도(CodeDeploy 환경설정)

github action이 돌아가는 원리에 대해 좀더 공부해보기로 했습니다.

  1. Github Action에서 프로젝트 빌드 후, jar 파일을 압축해서 정적 파일을 저장하는 S3에 업로드

  2. Github Action이 CodeDeploy에게 S3에 있는 jar 파일을 배포하라고 전달.

  3. CodeDeploy는 배포할 EC2 인스턴스 내부에 있는 CodeDeploy Agent에게 배포 명령을 내리고, Agent는 jar 파일을 S3에게 받아서 주어진 쉘 스크립트에 따라 배포를 진행

  4. 새로운 Spring Boot WAS를 띄우고, Nginx 스위칭을 통해 무중단 배포를 할 수 있도록 Agent에게 배포 스크립트를 제공합니다.
    출처: https://wbluke.tistory.com/39 [함께 자라기:티스토리]

여기서 유심히 볼게 3번입니다! os는 리눅스가 맞지만 jar파일을 EC2에서 읽는 것은 CodeDeploy agent 였습니다. 그럼 CodeDeploy에 환경변수를 설정해주면 agent 가 잘 읽어내지 않을까 싶어서 도전해봤습니다!
https://godngu.github.io/aws/codedeploy-envrionment-variable
이 블로그를 참고하여 codedeploy.sh라는 스크립트 파일을 만들고 export로 환경변수 설정을 해주었습니다. 그리고 여기서 중요한 점 !

codedeploy 재시작

sudo service codedeploy-agent restart

이 재시작을 해주어야 설정한 환경변수를 인식할 수 있었습니다.
프로그램에서 환경변수 같은 설정 후에는 재부팅을 해줘야 세팅이 완료된다고 들었지만 저도 이 부분을 놓치고 있다가 소중한 2시간을 또 삽질했습니다..

부푼 마음으로 push를 해본 결과는

드디어 성공입니다 ㅎㅎ 이렇게 jar파일을 잘 읽어서 배포에 성공하면 nginx에서 스프링부트 8081 포트와 8082 포트를 교체시키며 서버를 올립니다.

결론

프로그램이 내 맘대로 실행되지 않으면, 침착하게 작동하는 원리를 먼저 고민해보는게 좋을 것 같습니다.
구글링을 통해 '이렇게 하니 되더라' 느낌의 블로그 하나 보고 해결할 수도 있지만, 흐름을 파악하고 접근했을 때 훨씬 더 많이 배워가는 경험을 했습니다!

profile
처음처럼

0개의 댓글