aws-glue-lib 이미지로 AWS Glue를 로컬에서 개발하기

Kanto(칸토)·5일 전
0

프로그래밍

목록 보기
5/5
post-thumbnail

AWS Glue는 유용한 서비스 이지만 개발이 간편하지 않다. 그 이유는 Spark를 Serverless로 사용하는 서비스이며, Spark를 Glue라는 이름으로 감쌌기 때문에 내부 구조를 명확하게 이해하는게 어렵기 때문이다.

일반적인 상황이라면 공식 문서로 충분하다. https://docs.aws.amazon.com/ko_kr/glue/latest/dg/aws-glue-programming-etl-libraries.html

하지만, 사설 인증서가 개입되면 이야기는 달라진다. 각 시스템의 상황에 따라서 사설인증서를 이용해야만 VPN을 통한 AWS 접근이 가능하다던지, AWS의 S3 버킷의 접속시 443 port를 통한 TLS 프로토콜로만 제한하는 경우 등이 있을 수 있다.

이 문제를 해결하기 위한 정보가 충분하지 않아서 시간이 많이 걸렸다. 하지만 여러가지 노력을 통해서 정답을 얻을 수 있었으니 문제를 겪고 있는 개발자들에게 도움이 되면 좋겠다. (결정적 단서를 찾아준 ChatGPT도 답을 찾아 가는데 많은 단서를 필요로 했다.)

--

  1. Glue Local환경 구성
    먼저, Glue Local환경을 구성하지 못한 경우도 있을 수 있는데 그런 경우라면 아래 영상을 참고하여 Glue Local환경을 구성하자. 가장 깔끔한 것은 역시 Docker 방식이다.
    https://www.youtube.com/watch?v=-4ZnJkM-QDk

그리고 우리가 테스트하려고 하는 script는 공식 문서에 나온 아래 파일이다.

import sys
from pyspark.context import SparkContext
from awsglue.context import GlueContext
from awsglue.job import Job
from awsglue.utils import getResolvedOptions


class GluePythonSampleTest:
    def __init__(self):
        params = []
        if '--JOB_NAME' in sys.argv:
            params.append('JOB_NAME')
        args = getResolvedOptions(sys.argv, params)

        self.context = GlueContext(SparkContext.getOrCreate())
        self.job = Job(self.context)

        if 'JOB_NAME' in args:
            jobname = args['JOB_NAME']
        else:
            jobname = "test"
        self.job.init(jobname, args)

    def run(self):
        dyf = read_json(self.context, "s3://awsglue-datasets/examples/us-legislators/all/persons.json")
        dyf.printSchema()

        self.job.commit()


def read_json(glue_context, path):
    dynamicframe = glue_context.create_dynamic_frame.from_options(
        connection_type='s3',
        connection_options={
            'paths': [path],
            'recurse': True
        },
        format='json'
    )
    return dynamicframe


if __name__ == '__main__':
    GluePythonSampleTest().run()
  1. 처음엔 443 port 정책이 s3 버킷 정책에 적용되었는 몰라서 SSL을 disable 시켜봤다. 하지만 공식 public bucket인 예제와 다르게 실제 private 데이터에 접근하려고 하니 이내 Forbidden Error가 떴고, 원인은 TLS 정책 때문이었다. 만약 TLS가 아닌 방식으로 접근이 가능하다면 가장 간단한 방법이 아닌가 한다. 공식 문서도 TLS를 항상 false로 두는 예제를 사용하고 있다.
  1. 처음에는 aws-glue-libs(https://github.com/awslabs/aws-glue-libs/tree/glue-3.0) 이미지를 그대로 활용하려고 했고, 대신 pyspark 스크립트 안에서 변경을 해보려고 했다. 사설 인증서가 volumn mapping이 되어있다면, 내가 지정한 docker 내부의 경로에 이미 있을 것이기 때문에 그 경로를 지정해주면 된다고 생각했기 때문이다.
    예를 들자면, sc객체를 따로 선언한 뒤,_jsc.hadoopConfiguration()을 이용한 것이다. 하지만 아래 방법은 모두 작동하지 않았다. Glue의 로컬 이미지인 만큼 아마도 기본적으로는 이미 적용이 되어있었던 것 같다. 특히 검색을 하다 보면 region을 지정하라는 조언이 많은데, 내 경우는 pycharm에서 이미 aws credential과 config를 들고 들어가 수 있는 plugin을 사용했기 때문에 이로 인해 문제가 해결되지는 않았다. 하지만 이렇게 프로그래밍 방식으로 접근하는 것은 좋은 시도이다.
self.sc = SparkContext.getOrCreate() 
self.sc._jsc.hadoopConfiguration().set("fs.s3a.connection.ssl.enabled", "true")
self.sc._jsc.hadoopConfiguration().set("fs.s3a.ssl.channel.mode", "Default")
self.sc._jsc.hadoopConfiguration().set("fs.s3a.ssl.cacert.file", "/etc/ssl/certs/사설인증서.crt")
self.sc._jsc.hadoopConfiguration().set("fs.s3a.endpoint", "s3.ap-northeast-2.amazonaws.com")
self.sc._jsc.hadoopConfiguration().set("fs.s3a.impl", "org.apache.hadoop.fs.s3a.S3AFileSystem") 
self.sc._jsc.hadoopConfiguration().set("fs.s3a.connection.ssl.debug","true")
  1. 이후 java keystore의 정체를 알게되었다. java application에서는 ssl인증서를 경로로 관리하는게 아니라 keystore라는 서비스에서 관리한다는 것을 알게되었다. 그래서 keytool이라는 유틸리티를 사용하면 keystore에 내 사설 인증서를 저장할 수 있는 것이다.
keytool -import -trustcacerts -keystore /home/glue_user/.certs/container_certs/keystore.jks -storepass changeit -alias mycert -noprompt -file /home/glue_user/aws_helper/사설인증서.crt

여기서 중요한 것은 keystore를 저장하는 위치이다. aws glue 이미지에서는 /home/glue_user/.certs/container_certs/ 이곳이 default이다. 최초에 이 이미지 docker 컨테이너에 진입하게 되면 localhost.jks 라는 self-signed certificate를 만드는 것을 확인할 수도 있다. 이후에 이 과정을 자동화 하기 위해서 Dockerfile내부의 RUN 명령어로 미리 삽입할 것이다.

  1. 마지막으로 가장 중요한 것은 spark 설정을 추가하는 것이다. spark-defaults.conf는 이미 glue의 이미지에 맞게끔 설정되어 있지만 여기에다가 사설 인증서가 있는 java keystore를 사용할 수 있도록 spark-default.conf의 내용을 추가해줄 것이다. app.name 이나 master loca[4]는 이 troubleshooting 내용과 무관하게 그냥 custom 목적으로 설정해둔 것이니 이 부분은 사용하지 않아도 좋다. 중요한 것은 spark-default.conf를 새로 만들어서 copy하거나 덮어씌우지 말고 기존에 있는 파일이 컨테이너 내부에 이미 존재하고 있으니 그 파일에 append를 아래와 같이 한다는 점이다. 설정 항목은 extraJavaOptions이고 대상은 Djavax.net.ssl.trustStore 변수이다.executordriver 모두 설정해야 하는 점이 중요하다.
RUN echo "spark.app.name MySparkApp" >> $SPARK_CONF_DIR/spark-defaults.conf && \
    echo "spark.master local[4]" >> $SPARK_CONF_DIR/spark-defaults.conf && \
    echo "spark.executor.extraJavaOptions -Djavax.net.ssl.trustStore=/home/glue_user/.certs/container_certs/keystore.jks -Djavax.net.ssl.trustStorePassword=changeit" >> $SPARK_CONF_DIR/spark-defaults.conf && \
    echo "spark.driver.extraJavaOptions -Djavax.net.ssl.trustStore=/home/glue_user/.certs/container_certs/keystore.jks -Djavax.net.ssl.trustStorePassword=changeit" >> $SPARK_CONF_DIR/spark-defaults.conf
  1. Dockerfile을 작성
    이제 기존 이미지를 base로 하여 새로운 이미지를 작성할 것이다.
FROM amazon/aws-glue-libs:glue_libs_3.0.0_image_01

WORKDIR /home/glue_user/workspace/glue

ENTRYPOINT ["bash"]

USER root

COPY 사설인증서.crt /home/glue_user/aws_helper/

RUN mkdir -p /home/glue_user/.certs/container_certs
RUN keytool -import -trustcacerts -keystore /home/glue_user/.certs/container_certs/keystore.jks -storepass changeit -alias mycert -noprompt -file /home/glue_user/aws_helper/사설인증서.crt

# Append new configuration to spark-defaults.conf
RUN echo "spark.app.name MySparkApp" >> $SPARK_CONF_DIR/spark-defaults.conf && \
    echo "spark.master local[4]" >> $SPARK_CONF_DIR/spark-defaults.conf && \
    echo "spark.executor.extraJavaOptions -Djavax.net.ssl.trustStore=/home/glue_user/.certs/container_certs/keystore.jks -Djavax.net.ssl.trustStorePassword=changeit" >> $SPARK_CONF_DIR/spark-defaults.conf && \
    echo "spark.driver.extraJavaOptions -Djavax.net.ssl.trustStore=/home/glue_user/.certs/container_certs/keystore.jks -Djavax.net.ssl.trustStorePassword=changeit" >> $SPARK_CONF_DIR/spark-defaults.conf

#부가 패키지
RUN pip3 install awswrangler

USER glue_user

WORKDIR /home/glue_user/workspace

이제 끝났다. Glue 로컬 개발을 시작해보자.

profile
통계학으로 사람들의 행동을 이해하고 싶습니다.

0개의 댓글