한국농수산물유통공사에서는 kamis라는 데이터 정보 공개 시스템이 있습니다. 이따금 실습용으로 데이터를 불러와서 과일, 채소 시세를 만들어보는 목적으로 데이터를 수집해보는 편입니다. 과거에는 직접 python 코드로 작성해서 데이터를 불러왔는데, 서버를 띄우지 않아서 파이프라인까지는 완성하지 못했습니다. 이번에는 n8n을 공부하면서 low code를 no code 툴로 바꿔서 수집해보고 그 차이에 대해서 한번 고찰해봅니다. 


1. 개요

1.1. 과거 데이터 분석 결과 

과거 월간 데이터 노트에서 수행했던 결과를 한장의 포스터로 저장했습니다.

클릭하면 이미지가 커집니다!

 

1.2. 왜 이 분석은 아직 성불하지 못했는가?

위 분석은 사실 n년전 사이드 프로젝트를 하면서 구현한 결과입니다. 장바구니 대시보드라는 이 주제는 사실 지속성있게 데이터를 수집해야만 의미를 가집니다. 하지만 당시 로컬에서 retrospective 그러니까 현재 기준 과거 날짜에 대한 데이터만 수집했습니다. 지속가능한 프로덕트를 위해서는 서버에 올려서 자동화 및 파이프라인을 구축해합니다. 그래야 대시보드의 본질을 찾을 수 있기 때문입니다.

하지만 당시 이리저리 우선 순위에 밀려 단적인 그래프를 그리고 마치게됩니다. 클라우드에 서버를 빌리고 수집하는 과정이 어려운 일은 아니지만 번거로웠기 때문입니다. 그렇게 시간이 지나 월간 데이터 노트라는 스터디에서 다시 한번 부활하게 되었습니다. n8n이라는 자동화 도구를 이용하면 좀 더 편하게 데이터를 수집할 수 있을까요? 

2. 결과:  workflow, 데이터

Google Sheet 에서 데이터를 주고 받으려면 n8n 클라우드에서는 노드만 추가하고 구글 로그인을 하면 됩니다. 하지만 셀프 호스팅의 경우 인증을 위한 사전 절차가 필요합니다. 다음 n8n 문서에 잘 나와있어서 따라하기만 하면 됩니다.

https://docs.n8n.io/integrations/builtin/credentials/google/oauth-generic/#google-cloud-app-becoming-unauthorized

완성된 work flow
결과

3 . n8n 도입 후기

장점1:  간단한 세팅

기본적으로 n8n은 오픈소스여서 내 컴퓨터에 설치해서 실행할 수 있습니다. 하지만 이 과정이 조금 복잡하기 때문에 14일간의 무료 trial 기간을 제공합니다. 셀프호스팅을 하지 않고 진행할 수 있는 점이 장점입니다. 로컬에서 수집하려면 파이썬 설치, 가상환경 세팅 등이 번거롭습니다. 하지만 n8n 은 node로 이루어진 구성 요소를 선택하고 연결함으로써 쉽게 원하는 목적을 달성할 수 있습니다. 

장점2: 시각화, 쉬운 모듈화

이따금 복잡한 서비스를 만들다 보면 코드레벨에 있는 스키마를 보여주기 위하여 figjam과 같은 3rd party tool이용해서 그려야합니다. 하지만 n8n은 그 자체로 로직이 보이기 때문에 굳이 스키마를 만들기 위한 시간과 노력을 기울이지 않아도 됩니다. 또한 노드들 하나 하나의 input ouput이 명확하게 보여서 어떻게 작동하는지 파악하기 쉬웠습니다.

Input -> 개별 노드의 파라미터 -> Output

장점3: 좋은 레퍼런스들

입문자들에게는 n8n에서 제공해주는 혹은 강의나 블로그에서 제공해주는 .json형식의 workflow를 따라하는 것만으로도 도움이 됩니다. 공식 문서는 제가 근래 본 오픈 소스 중에 가장 이해하기 편했으며, 포럼도 잘 활성화 되어있습니다. 서비스 안에서는 사이드바에서 LLM assist를 받을 수 있습니다. 생각해보니 LLM assist는 클라우드에서만 제공되는 단점이 있네요.

(좌) Docs (우) Forum
workflow 화면 사이드에 chatbot이 존재

 

단점1: 어쨋든 알아하는 작동 방식

노코드 툴이 가지는 전체 문제점이기도 하나, 각 개별 노드가 작동하는 방식을 어렴풋히라도 인지 하지 않으면 설계 자체가 불가능합니다. HTTP 노드에서 수행하는 POST/GET 요청의 차이 받아 처리하는 JSON 자료형, API의 개요, 구글 시트를 연결하기 위한 인증 과정들에 대한 이해가 있거나 해결하려는 마음이 있어야 원하는 결과물을 얻을 수 있습니다. LLM에서 "해줘"라고 요청했을 때 바로 수행하는 그런 경험을 기대했다면 실망할 수 있습니다.

단점2: 셀프호스팅의 할 경우 올라가는 러닝커브

n8n을 최초 회원가입하면 14일간의 무료 Trial을 주지만 결국 끝나면 유료 결재를 하게 될 것입니다. 비용을 피하고 싶다면 셀프 호스팅을 해야하는데 docker등에 대한 도구를 사용하므로 자연스럽게 러닝커브가 올라갑니다. 개발에 익숙하면 어렵겠지만 결국 노코드의 장점을 일부 잃어버린다고 할 수 있겠습니다.

4. 짤막한 후기

로우 코드로 직접 구현하고 만드는 과정이 꽤나 귀찮았는데 n8n으로 하니 파이프라인도 쉽게 만들 수 있는 장점이 있는 것 같습니다. 비개발자를 위한 툴이기도 하지만 개발자들도 쉽게 만들 수 있는 툴인 것 같고 Make, Zaiper 등이 가지고 있는 자동화 라인에서 굳건히 자리를 지킬 것 같네요! 종종 쓰게 될 것 같습니다.

5. 출처

6. 기타 팁

  • 로컬 docker에서 구글 시트와의 정보를 주고 받으려면 인증 진행 필요
  • n8n 클라우드의 경우 인증 버튼이 바로 있지만 셀프호스팅은 그렇지 않기 때문에 Outh 인증을 진행

  • OAuth Redirect URL:  구글 인증 플랫폼 - 클라이언트 - 승인된 리디렉션 URL에 넣을 것,  로그인 요청시 받은 API 엔드포인트 경로인듯
  • Client ID / Secret: GCP 위 경로에서 웹 어플리케이션으로 생성하면 나오는 ID/PW

 

본 주제는 매달 한 번씩 호기심을 주제로 분석하는 모임 <월간 데이터 노트>의 결과입니다. 관심이 있으시면 다음 링크를 확인해 보세요!!

자체적으로 채점자동화를 위한 웹 서비스를 만든 후기를 작성해봅니다. 소규모 인원을 대상으로 GCP 서비스를 조합해서 편하게 채점할 수 있도록 만들었고 그 과정에서 개발, 배포, 서비스 운영에 대한 전반적인 내용을 다룹니다. 그 과정에서 일어난 시행 착오를 공유하여 독자들이 서버리스 자동화를 설계할 때 사전에 고려해야할 점 등을 예습하는 글이 되길 바랍니다.


1. 채점 서비스의 필요성

1.1. 기존 채점 프로세스의 문제점

  • 문제1) 과제 제출 -> 피드백 까지의 시간 지연

기존에는 학생들이 과제를 제출하면 이를 받아 채점 튜터들이 채점하고 리뷰를 하는 구조로 진행 되어있습니다. 

학생들의 답안 제출 -> 교육 시스템에 적재 -> 튜터에게 할당(10명 등) -> 각자의 로컬 컴퓨터에서 코드를 실행 & 리뷰를 작성

답안 제출로부터 학생들이 피드백을 받을 때까지 최소 1주 - 최대 2주의 시간이 소요되었습니다. 문제를 푸는데 이렇게 시간이 오래 걸린다면 코드를 작성할때의 기억들이 휘발되어 학습효과가 떨어집니다. 따라서 위 프로세스 중에  채점하는 시간을 줄이고자 했습니다.

  • 문제2) 환경 의존성 문제

데이터 분석을 포함한 프로그래밍은 환경 설정에 따라서 그 결과값의 차이가 있을 수 있습니다. 예컨데 SQL로 데이터를 불러온다고 해도 각자 데이터를 불러오는 DDL/DML이 다르거나 환경이 다르면 다른 결과물을 산출할 수 있습니다. 과제를 풀 때나, 채점을 할 때나 각자의 로컬 환경에 MySQL-DBeaver로 설치를 하는 환경이 문제였습니다. 

따라서, 위 문제를 해결하기 위해서 하나의 서비스를 만들게 되었습니다. 제출된 코드에 대한 결과는 채점 자동화는 물론, 코드에 대한 정성적 피드백도 Gemini로 받았으면 좋겠다고 생각했습니다.

2. 채점 시스템 설계

2.1. 기존 프로세스 개선

사실 서비스를 시작하기에 앞서 이 서비스를 전체적으로 알리고 프로세스를 개선하는 필요성이 있다고 생각 했습니다. 따라서 과제를 제출하는 과정부터 프로세스를 개선하기 위해서 교육업체 담당자에게 설명하기 시작했습니다. 

보통 과제가 나가는 과정은  튜터의 과제 제작 -> 학생들이 풀이 -> 튜터들의 과제 채점 -> 결과 송부 의 형식으로 진행됩니다. 일관된 채점을 위해서는 먼저 과제 제작에 대한 설계도 필요했습니다. 그래서 과제를 제출할 시 몇가지 기준을 세웠습니다. 

  • 과제에 대한 의도, 목적 등을 작성할 것
  • 과제에 채점 기준을 명시할 것
  • 모범 답안으로 해설지를 작성할 것(정답 코드 포함)

이렇게 되면 일관된 과제 제출도 가능해지고 추후 채점을 Gemini에 요청할 때 Prompt 의 기반이 될 것이므로 이 형식을 같이 잡아나갔습니다. 

2.2. 채점 서비스 설계

위 업무 프로세스 개선과 동시에 서비스 설계를 위한 서비스와 스택을 정리해나갔습니다. 가장 중점적인 것은 GCP의 on-demand 서비스인 Cloud run으로 서버리스 구조를 선택하게 되었습니다. 서버를 관리하고 올리기엔 관리포인트가 클 것이고 간단하게 만들 수 있을 것 같아 선택했습니다.(하지만 이 선택을 후회하게됩니다) 웹사이트는 Streamlit으로 정했고, . 또한 과제는 크게 SQL와 Python으로 나뉘는데 SQL의 경우 실제 MySQL 8.0 버전이 필요해서 GCP의 Cloud SQL을 사용하였습니다. 파이썬의 경우 내장함수으로 exec 를 이용해서 채점 기능을 구현했습니다.

  • 기술 스택
    • 서비스: Cloud run, Cloud SQL, Gemini(gemini-2.0-flash)
    • 백엔드/프론트: Python, Steamlit(Python)

과제는 확장될 수 있지만 기본적으로 SQL, Python, Pandas에 대한 문제를 기본으로 결과를 만들어 갔습니다. 크게 기능은 모범 정답과 학생 정답을 채점하는 기능 1번과 정성 평가를 하는 기능2가 있습니다.

  • 기능 1) Gemini를 이용한 정성평가( 코드의 효율성과 개선점을)을 함께 평가
    • 사전에 선정해놓은 기준으로 Prompt engineering에 따라 점수를 측정
  • 기능 2) 학생들의 답과 모범 정답 비교
    • SQL)  Cloud상의 SQL 서비스에 직접 쿼리를 날려 가져와 출력
    • Python) exec 내장함수를 이용해서 테스트 케이스마다의 출력
    • Pandas) 코드저장소의 csv파일을 불러와 데이터 가공한 결과를 출력

3. 핵심 기능 구현 상세

기능1) 정성 평가

  • 구현 내용

정성 평가는 몇가지 기준에 따라 채점됩니다. 2.1. 기존 프로세스 변경 제안에서 말한 것처럼 과제에 대한 의도, 채점 기준, 모범 답안 등의 대한 정보와 사전 프롬프트를 받아 결과를 뱉어냅니다. 다음은 `prompt_builder.py` 의 결과물이며 Langchain의 from_template 메소드처럼 사전에 변경 가능한 구조로 설계하여 확장정을 높였습니다.

당신은 {category} 과제 채점 전문가입니다.

- 문제: {question}
- 모범답안: {model_answer}
- 학생답안: {student_answer}
- 평가기준: {evaluation_criteria}
- 쿼리 상태: {query_status}
{grading_criteria_str}
학생의 답안이 모범답안과 다르더라도, 논리적/문법적 오류가 없다면 높은 점수를 주세요.
아래 조건을 꼭 지켜서 평가해 주세요.

1. 각 평가기준(정확도, 가독성, 효율성, 분석력 등)에 대해 **5점 단위(0, 5, 10, ..., 100)로만** 부분 점수를 각각 매겨주세요. (예: 정확도 50점, 가독성 20점, 효율성 15점)
2. 각 기준별 점수의 합이 100점이 되도록 최종 점수도 함께 출력해 주세요.
3. 각 평가기준별로 '참고할 점'을 한두 문장으로 구체적으로 작성해 주세요. (예: 잘한 점, 부족한 점, 개선점 등)
4. 마지막 피드백은 평가자가 참고할 수 있도록, 학생에게 직접 전달하는 친근한 말투가 아니라 틀린 부분, 부족한 점, 개선점 등 객관적이고 구체적인 정보 위주로 작성해 주세요.

결과는 아래 형식으로 출력해 주세요.

[기준별 점수]
정확도: 45 / 50
가독성: 15 / 20
효율성: 10 / 15
분석력: 10 / 15

[기준별 참고사항]
정확도: WHERE 조건에서 'Attrited Customer' 필터가 누락됨.
가독성: 쿼리 구조는 명확함.
효율성: CTE 활용이 부족함.

[최종 점수] 80
[피드백] 전반적으로 쿼리 구조는 명확하나, 일부 필터 조건과 효율성에서 아쉬움
  • 문제: 프롬프트에 대한 불확실성

결국 LLM에 응답한 결과는 매번 결과가 달라지는 문제가 있습니다. 특히 이따금 결과값이 json 형태로 뱉어내어서 기대한 형태의 출력이 안되는 경우도 많았는데, 추후에 Langhain 스택을 추가하여 StructuredOuputParser 사용하는게 좀 더 나을 것 같습니다. 

기능2) 채점 기능 - SQL

  • 구현 내용

SQL은 MySQL로 문제를 냈다면 해당 클라우드가 있어야합니다. 이를  위해 Cloud SQL에 인스턴스를 하나 파서 DDL을 이용하여 데이터를 삽입하였습니다. 문제는 공통된 DDL을 코드로 제시했음에도 불구하고 각자의 환경에서 알아서 데이터를 불러오다보니 미묘하게 결과 데이터가 다른 문제가 있었습니다. 예를 들어 정수/정수 나누기에 대한 결과값 계산 과정에서 decimal 사용 유무에 따라 결과 값이 달라서 결국 소수점 2째짜리 이내는 정답으로 처리하도록 임시 방편을 택했습니다. 

  • 문제: 비용 이슈

반면 cloud 스펙에 대한 이슈도 있었는데, 원본 데이터가 그렇게 크지 않지만 초기 스펙은 램 2기가에 2CPU를 기본값을 배정했는데 매일 5천원씩 너무 많이 과금이 되는 문제가 있었습니다. 샌드박스 옵션인데도 불구하고 과제에 적합한 사이즈는 아니 였으며, DB의 완정성을 보장하기 위한 백업이나 관리의 비용이 많이 나가는 것을 확인했습니다. 이를 확인하고 다운 그레이드 절차를 진행했습니다. 결과적으로 일 5000원 유지비 -> 850원 정도의 유지비로 절감할 수 있었습니다.

  • 데이터 백업 기능 해제(운영 DB가 아니므로)
  • CPU 2코어 수 -> 1 햐향 조정
  • 메모리 2GB -> 600 MB 하향 조정

기능2) 채점 기능 -  Python / Pandas

  • 구현 내용

파이썬의 경우내장 함수 exec를 통해 채점을 구현했습니다. 문제를 dictionary  형식으로 저장하여 추후 불러와 사용했습니다. python의 결과는 모범정답의 key값에 따라 다양한 체크포인트를 거쳐 최종 정답으로 처리됩니다. 예컨데 문제1번이 단순하게 평균을 내는 함수라면 model_answer의 코드를 exec 내장함수에 넣고 test_cases의 input값을 전달인자로 실행하여 expected와 같은지 확인하면 됩니다. 

QUESTIONS = {
    "PYTHON_1": {
    "title": "숫자 리스트의 평균 계산하기",
    "content": 
    	"""
        - **배경**: 한 소매점에서 재고를 계산해야 합니다. 주어진 재고의 평균을 계산해보세요.
        - **문제 의도**
            - 리스트의 자료형을 이해
            - 내장 함수의 활용
        - **요구 사항**
            - 함수명: `calculate_stock`
            - 해당 함수는 리스트의 전달 인자를 받음
        """,
    "model_answer": 
        """
        def calculate_stock(numbers):
            return sum(numbers) / len(numbers)
        """,
    "function_name": "calculate_stock",
    "test_cases": [{"input": [10, 20, 30, 40, 50], "expected": 30.0}],
    "evaluation_criteria": [
            {"id": "P1", "description": "리스트의 합과 길이를 이용해 평균을 계산한다."},
            {"id": "P2", "description": "내장 함수(sum, len) 사용"}
            ]
    }

 Pandas의 경우 자료형과 결과값 두개를 동시에 비교해야 하기때문에 "test_cases"에 자료형을 저장하는 "expected_type"과 "expected"를 동시에 넣어 채점하게 됩니다. passed 변수를 True로 초기화 해놓고 각 기준을 통과하지 못하면 False으로 Flag를 세워 최종적으로 True로 살아남는 코드를 정답으로 처리하였습니다.

"PYTHON_7": {
        "title": "결측치 확인",
        "content": 
            """
            - **배경:** 데이터를 불러 왔을 때 각 컬럼에 결측치 유무를 확인하는 것은 중요합니다. 컬럼의 결측치를 확인해보세요.
            - **문제 의도**
                - DataFrame의 함수를 활용
            - **요구 사항**
                - 함수명: `get_missing`
                - 컬럼별 결측치 수를 예시 결과와 같이 출력
            """,
        "model_answer": 
            """
            def get_missing(df):
                return df.isnull().sum()
            """,
        "function_name": "get_missing",
        "test_cases": [ {"input": "df_sample",
           "expected_type": 'Series',
           "expected": {  
                "Route": 1,
                "Total_Stops": 1,
                "Airline": 0, "Date_of_Journey": 0, "Source": 0, 
                "Destination": 0, "Dep_Time": 0, "Arrival_Time": 0,
                "Duration": 0, "Additional_Info": 0, "Price": 0
            }}
        ],
        "evaluation_criteria": [{"id": "P1", "description": "DataFrame의 isnull().sum()을 활용한다."}]
        }

또한, 데이터를 불러와야하는 문제가 있었는데, 매번 문제마다 데이터를 불러오면 in-ouput관점에서 비효율적일 것 같아서 찾아보니 stremlit에서 캐시 기능을 구현할 수 있는 데코레이터를 발견했습니다. 이를 사용해서 효율적인 저장을 구현했습니다.

@st.cache_data
def load_sample_dataframe():
    """
    이 함수는 처음 호출될 때 단 한 번만 실행되고 캐싱됩니다.
    이후 호출에서는 캐시된 데이터를 반환합니다.
    """
    # print('데이터로드 시작')
    df = pd.read_csv('./data/7th/flight_data.csv', sep=';')
    # print(df.head(3))
    # print('데이터로드 종료')
    return df
  • 이슈: 데이터 로딩 문제

문제는 Pandas에 있었는데, 데이터 분석에 관련하기 때문에 외부에서 데이터를 불러와서 채점하게 됩니다. uci 레포나 github 등일수도있고 mysql 서버일 수 도 있습니다. 하지만 cloud service은 외부 데이터를 가져옴에 있어서 인증을 요구하다보니 이걸로 3일 정도 삽질을 했습니다. 사실 별거아닌 문제이지만, cloud run 서비스 특성상 에러를 찾기가 매우 불편합니다. 로그를 뒤져보면 되긴하지만 내가 실제로 서버를 띄우는 것 처럼 bash형태로 로그를 찾는게 아니다 보니 데이터 로딩에서 문제가 일어난다는 사실 자체를 찾는데도 오래 걸렸습니다.  사실 데이터가 7MB 이였어서 큰 문제는 없었지만, 대량의 데이터를 불러온다면 Cloud SQL 에 적재해서 가져오는 방향이 나을 것 같습니다. 

4. 개발 과정의 교훈과 팁

  • 각 디렉토리를 정확하게 명시
― core: 핵심 모듈
┕ service: 외부 호출되는 서비스( Ex Cloud SQL 등)
┕ streamlit_app: 프론트 엔드 streamlit 을 위한 디렉토리
┕ answer: 정답 저장
┕ data : 데이터 저장

하나의 디렉토리는 하나의 기능 혹은 구현하려고자하는 결과물이 명확하게 정의되면 정리하기가 좋습니다. 혼자 일하던 여럿이 일하던 명확한 디렉토리 설계와 README.md 작성은 업무 시작 시 로딩 속도를 줄여줍니다.

  • 생성된 모듈의 import 하는 방법
# module import 문제 없음. 같은 경로
/ main.py
/ module.py

# module import 문제 없음. 같은 경로
┖ core
┖ ─ main.py
┖ dir
┖ ─ __init__.py
┖ ─  module.py

위처럼 모듈을 기능에 따라 나누다보면 결국 타 디렉토리에 모듈을 가져올 상황이 생깁니다. 같은 경로면 import module.py 를 스크립트 상단에 올리면되지만 디렉토리가 서로 다른 경우 문제가 생깁니다. 이럴때 `__init__.py` 라는 초기화 모듈을 해당 디렉토리에 넣어주면 경로를 쉽게 인식할 수 있게 해줍니다.

  • Docker를 적극적으로 사용하자

streamlit 을 이용하면 localhost:8501 포트에 자동으로 실행되지만 여기서 재현성이 된다고 Cloud run에서 잘 작동하는 것 은 아닙니다. 기본적으로 로컬은 윈도우 환경이고 cloud 서비스는 linux  환경이기 때문입니다. 그래서 실제로 컨테이너를 띄워서 테스트해보는게 cloud run에 배포했을 때도 오류를 줄일 수 있는 좋은 테스트 환경입니다. Docker 짱장맨!

# base 디렉토리로 이동
cd base

# Docker 이미지 빌드
# 맥이라면 arm 64 아키텍쳐로 빌드하고 테스트하며, 배포 시에는 arm64로 변경필요
docker build --platform linux/amd64 -t {서비스명} .

# 빌드된 이미지로 Docker 컨테이너 실행하기
docker run -d -p 8501:8080 -m 1g --name {개발 이미지명} {서비스명}
  • [중요] build후 로컬 테스트에서 윈도우에서는 amd64, 맥에서는 arm64 아키텍쳐를 명시해야하나, cloud run 배포시는 amd64로 통일 필수(이것 때문에 단순배포 2시간동안 삽질함)

https://cloud.google.com/run/docs/building/containers?hl=ko

 

기타 유틸 파일 활용법

  • .env: 환경 변수에 대한 정보를 담습니다. 보통 api나 db의 접속 정보 등을 가지고 있으나 credential이기 때문에 `.env .example` 파일을 github에 올리고 환경변수 명을 제외한 정보를 제거하여 형태만 보존합니다.
# .env.example
GEMINI_API_KEY=
SLACK_BOT_TOKEN=
  • `.dockerignore`: 도커나이징 할때 굳이 필요 없는 파일을 저장합니다. `.gitignore`와 비슷한데 보통 python 가상환경에 대한 디렉토리나 cache 기타 파일들을 작성합니다. 이걸 명시하지 않으면 가상환경 디렉토리 내용이 통째로 docker화 되기 때문에 작업 효율이 늦어집니다.
# 파이썬 가상환경 폴더
.venv

# 파이썬 캐시 파일
__pycache__/
*.pyc

# Git 폴더
.git

# 기타 운영체제 파일
.DS_Store
  • 배포 코드 .sh 화: 일련의 deploy를 위한 코드를 매번 치지말고 .sh 파일을 만들어서 실행시킵니다.

#!/bin/bash
# 스크립트 실행 중 오류 발생 시 즉시 중단
set -e

# 2. Docker 이미지 빌드 (linux/amd64 플랫폼)
echo "Building Docker image..."
docker build --platform linux/amd64 -t {서비스명} .

# 3. Artifact Registry에 맞게 이미지 태그 지정
echo "Tagging image for Artifact Registry..."
docker tag {서비스명} {이미지명}.pkg.dev/{프로젝트}/cloud-run-source-deploy/{서비스명}

# 4. Artifact Registry로 이미지 푸시
echo "Pushing image to Artifact Registry..."
docker push {이미지명}.pkg.dev/{프로젝트}/cloud-run-source-deploy/{서비스명}

# 5. Cloud Run에 배포 (us-central1)
echo "Deploying to Cloud Run..."
gcloud run deploy grading-app \
  --image={이미지명}.pkg.dev/{프로젝트명}/cloud-run-source-deploy/{서비스명} \
  --region=us-central1 \
  --platform=managed \
  --vpc-connector={vpc 커넥터} \
  --vpc-egress=all \
  --allow-unauthenticated \
  --memory=2Gi \
  --min-instances=1 \
  --cpu=2

echo "Deployment complete! 🚀"
  • requirements

requirement는 가상환경을 쓴다면 자주 사용하는데 이게 나중에 docker로 배포할때 설치할 라이브러리가 되기 때문에 필수적입니다. 

#가상환경 만들기
py -3.12 -m venv {가상환경명}

'''
라이브러리 설치 ...
'''

#패키지 저장
pip freeze > -r requirements.txt

#설치
pip install -r requirments.txt
  • mono repo전략

잠시 파이썬 채점자동화시 clodu run function을 고민했던 적이 있었습니다. 코드저장소 측면에서보면 하나의 리포지토리에 두 개이상의 배포 포인트가 있어야하는건데 이럴 땐 그냥 간단히 디렉토리를 나누면 됩니다.

5. 개발 후기

5.1. 그래서 cloud run 다음에도 쓸까?

상황에 따라 다를 것 같은데, 그래도 서버를 파는게 나을 것 같습니다. on-demand서비스라는건 비용 측면에서 좋지마 로그를 찾기가 너무 불편합니다. 데이터 로딩 과정에서 계속 에러가 났는데 확인하기 불가능한 경우가 많았습니다. 더욱이 로컬 Docker에서는 기능을 잘 하는 것이 배포하는 환경에서는 에러 재현이 안되니 정말 답답했습니다. 사실상 단일 스크립트로 구현해야하는 서비스의 경우에는 cloud run function을 사용하는게 나을 것 같고, 조금 복잡한 서비스에는 직접 GCE를 이용해서 구현하는게 디버깅 측면에서는 더 나을 것 같습니다. 

여기서 에러를 찾아보시오..

5.2. 개선할 점

  • 구조설계의 효율화

뒤돌아보니 개선할 몇가지가 보이긴 합니다. 일단 가장 큰 것은 함수의 파편화입니다. 일례로 gemini가 정성 평가하는 방식만 봐도 main -> local_grader -> generate_content -> prompt_builder 의 스택 구조가 필요 이상으로 만들어져 있다는 느낌이 들었습니다. 이러면 디버깅 관점에서도, 확장 측면에서도 파악이 어려워서, 다음에는 하나의 스크립트로 통합하고 prompt_builder 와 같은 지속적으로 변경하고 수정이 필요한 부분만 따로 분리하는 것이 좋을 것 같습니다. 또한, 함수형 스크립트보다는 문제의 기능을 통합하여 class 설계를 하는 것도 좋겠습니다.

  • 비용 효율 관점

처음에는 비용을 크게 생각하지 않았는데, 매일 몇천원씩 과금되는 Cloud - SQL이 점차 비용이 많이 증대되는게 느껴졌습니다. 좀 더 저렴한 사설 cloud를 쓸 수 도 있겠지만, 관리포인트가 많아지고 난이도가 높아질 것 같아 최대한 GCP안에서 해결하려고 했습니다. 하지만 그 과정에서도 자원과 비용을 효율적으로 사용할 설계를 하는 것이 좋겠습니다. 항상 채점 서비스가 올라가야하는 것은 아니라 상시 올려져있는 cloud-sql와 같은 서비스도 on-off 자동화가 필요해보입니다.

  • 채점 서비스 기능 추가

현재 채점 서비스는 따로 로깅이나 데이터 수집을 하고 있진 않습니다. Gemini를 단순히 사용하는 것에 넘어서 개선하는 결과를 만들고 싶은데 그러러면 로깅 시스템이 있어야해서 설계해볼 것 같습니다. 또한, 현재는 정량적인 결과를 채점하고 있는데, 추후 통계 그래프나 시각화 이미지 등을 채점하는 기능을 추가 개발할 여지가 있습니다. 그리고 현재는 Prompt Engineering이 직접 api를 call 하고 있는데 langchain을 도입하여 좀 더 구조적인 설계를 해볼 수 있습니다.

5.3. 결과

결과적으로 총 2번의 과제에 지원 서비스를 무사히 마칠 수 있었고, 하나의 서비스를 개발해보는 좋은 경험이 되었습니다. 정량적으로는 기존에 1명에 채점이 20분 정도 걸리는 시간에서 10분정도로 빠르게 채점할 수 있었고, 개별적으로 튜터들이 시스템을 구축하여 채점하는 사전 준비 시간도 없앨 수 있었습니다. 사실 본 서비스는 누가 시킨것도 아니고 스스로 편하게 일해보자고 시작했지만 그래도 잘 해보자 나에게 남을 것이라는 마음으로 시작했는데, 결과가 나름 만족스럽게 나와서 좋았습니다. 다음에도 이렇게 스스로 추진하고 결과를 만드는 프로덕트를 하나씩 만들고 남겨보도록 하겠습니다. 

6. 레퍼런스

https://github.com/llm-bot-sparta/llmbot/tree/main

 

GitHub - llm-bot-sparta/llmbot: 스코 채점&질문봇

스코 채점&질문봇. Contribute to llm-bot-sparta/llmbot development by creating an account on GitHub.

github.com

 

사이드프로젝트 혹은 사내 작은 프로젝트를 하기 위한 클라우드 서비스별 크레딧을 정리해보았습니다. 하기 내용은 정확하지 않을 수 있으며, 해당 서비스의 공식홈페이지를 참고하시는 것을 추천드립니다. 


문서버전: 2025-03-24 최초 등록

일반 사용자

  • NCP 10만원, 3개월
접근성이 쉬움 + Naver API 사용하기도 좋음
  • GCP 300 달러 크레딧, 3개월
GCP는 Computing resource 하나 조건무관 제공 (Disk 30GiB)
  • AWS 프리티어 12개월 무료(쿠폰적용시 100달러)
GCP와 달리 1년 지나면 무료 해지인듯!
  • Azure 200 달러
후발 주자여서 그런지 지원 정책이 올라오고 있는 중

 

사업자/스타트업

개인의견:

사업자 & 스타트업 신청은 굳이라고 생각하실 수도 있지만, 개인사업자의 경우 국내 홈택스에 쉽게 사업자를 낼 수 있습니다. 다만 청년창업소득세면제와 같은 쿠폰을 써버리는게 아깝다고 사업자를 내는걸 꺼려할 수도 있지만 사실 뭘 안하는것보다 일단 하는게 좋다는 생각입니다.  법인사업자의 경우 절차와 신고 등이 부가적인 것이기 때문에 굳이 추천 드리진 않습니다. 

 

 

이번 글은 슬랙 기반 커뮤너티에서 참여자들에게 독려의 CRM 메세지를 보낸 경험과 커뮤너티 안의 게임채널에서 방문알람 봇을 만든 사례를 작성합니다. CS에 대한 개념이 살짝 필요하긴 하지만 기본적인 API 개념과 파이썬을 활용하여 쉽게 만들어볼 수 있는 사례라서 API를 제공하는 커뮤너티 어플리케이션에서 유용하게 쓰는데 도움이 되길 바랍니다!


1. 개요

필자는 개발자의 글쓰기 모임인 글또10기에 참여하고 있으며 또한 운영진으로 활동하고 있다. 아무래도 개발자 기반의 커뮤니티다 보니까 글쓰는 활동이나 내부 커뮤너티 활동들을 봇을 이용해서 활용할 기회가 많다. 예를 들면, 글 제출을 한다던지 혹은 제출된 글에 대해서 LLM으로 피드백을 해준다던지 자동화가 되어 있는 기능들이 많다. 이번 글에서는 Customer Relational Managment(a.k.a. CRM) 운영진의 일환으로 설날 맞이 보낸 CRM 메세지와 게임채널에서 마인크래프트를 이용한 서버를 운영하고 출석체크 봇을 만든 경험을 남기고자 한다.

 

2. 글또 CRM 메세지 보내기

2.1. CRM 메시지 기획하기

우리의 커뮤너티는 글쓰기를 주제로 약 4-6개월간 지속되는 활동으로 그 기간동안 활동한 내역을 집계하면 재밌는 얘기가 많이 나온다. 글또에서는 CRM 봇인 지니 봇이 비정기적으로 지금까지의 활동내역을 보여주고 Activation을 넛지하는 전통(?)이 있다. 지난 기수도 역시 소소하게 이런 메세지를 보냈었고 반응이 되게 좋았었다. 아래는 이번기수에 내가 받은(사실 내가 보낸) 메세지이다. 

 

10기 실제로 보낸(그리고 내가 받은) 메시지

 

역시 10기에도 보내자고 얘기가 계속 나왔고 결국 설날에 맞춰 보내기로 하였다. 내가 CRM 팀으로 이전한 건 이번이 처음이었기 때문에 추상적으로 어떻게 하겠다는 감은 있어도 실제로 해본적은 없기 때문에 좀 더 명확하게 이 프로세스를 전수 받고자 자처하여 메세지 봇을 만드는데 지원하였다. 사실 메세지를 보내는 시스템은 구축이 되어있었다. CRM 팀이 처음인 나는 지니봇이 작동하는 프로세스를 명확하게 하고 팀원들에게 공유하면 좋겠다고 생각했고 figjam을 이용해서 다음과 같이 정리했다. 참고로 우리의 지니봇은 주로 커뮤너티에서 전달하는 쿠폰이나 개인화 메세지에 초점을 맞춰 전달하고 있다.


 
이 과정에 크게는 3가지의 단계가 있는데 

  1. (데이터 마트 구축): 기존 또봇이 수집한 데이터 Log를 기반으로 참여자들의 글 제출, 메시지, 스레드 댓글, 이모지의 데이터를 집계하여 Data Mart를 만드는 과정
  2. (지표 생성) 정보를 가지고 활동, 글, 커피챗 레벨을 지표로 만들고 친한 사람(팔로우)을 정의하는 과정
  3. (메시지 전송) 참여자의 정보를 토대로 총합된 1,2의 정보를 불러와 각자의 지표에 맞춰 슬랙 봇으로 전송하는 과정

각자의 R&R이 나뉘고 나의 역할인 메시지 전송에서 작업을 시작했다. 이는 동민님의 Github에 잘 정리되어 있어서 답습하기 편했다. 하지만, 지니 봇은 특정 이벤트에만 작동시키는 경우가 많기 때문에 CRM 팀에서도 자주 사용하는 스크립트는 아니다. 따라서, 먼저 이를 파악하기 위해 동민님의 Github 레포를 뜯기 시작했다.

https://github.com/ddongmiin/geultto_genie_bot

 

GitHub - ddongmiin/geultto_genie_bot: 개발자글쓰기 커뮤니티 글또에서 "참여자 활동 목표 개선"을 위해

개발자글쓰기 커뮤니티 글또에서 "참여자 활동 목표 개선"을 위해 데이터를 수집하고 데이터반상회,데이터야놀자에서 발표를 진행했습니다. - ddongmiin/geultto_genie_bot

github.com

 

여기서 자주 사용하는 script는 /core/bigquery.py로 글또의 데이터는 빅쿼리에 적재되기 때문에 데이터를 저장하고 불러오는 등에 대한 메소드가 구현되어 있다. 빅쿼리와 슬랙을 주고 받는 기능에 대하여 개념적으로는 알지만 희미해서 새롭게 스크립트(message_basic.py)를 만들었다.

'''
genie봇이 메세지를 보내는 기본기능을 정의합니다. token은 관리자 문의
해당 스크립트를 실행하면 번호로 선택하여 실행하며, 기능을 수행할 대상에 대한 정보(user_id 혹은 스레드정보)를 전달해야합니다.
'''
import sys
import os
print(sys.version)
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
from core.bigquery import BigqueryProcessor
bigquery_client = BigqueryProcessor(
    env_name="GOOGLE_APPLICATION_CREDENTIALS", database_id="geultto_10th"
)
SLACK_BOT_TOKEN = os.getenv("SLACK_BOT_TOKEN") # 윈도우 로컬 - 시스템 - 환경변수에 저장
USER_ID = ""  # 메시지를 보낼 대상의 Slack User ID


#하기 두개의 인자는 스레드의 링크복사 URL에 정보가 저장되어있음
URL = ''# 개인정보 이슈 삭제
URL_LIST = URL.split('/')
CHANNEL_ID = URL_LIST[4] #  채널 ID
THREAD_TS = URL_LIST[5][1:-7] + '.' + URL_LIST[5][-7:] #   스레드 메시지의 timestamp

# WebClient 초기화
client = WebClient(token=SLACK_BOT_TOKEN)

def send_message_to_user(user_id, message):
    '''
    Slack 메세지 전송 함수
    '''
    try:
        response = client.chat_postMessage(
            channel=user_id,
            text=message
        )
        # print(f"메시지가 성공적으로 전송되었습니다: {response['ts']}")
        return response['ts']
    except SlackApiError as e:
        print(f"오류 발생: {e.response['error']}")

def send_reply_to_thread(channel_id, thread_ts, message):
    '''
    스레드에 댓글 남기는 함수
    '''
    try:
        response = client.chat_postMessage(
            channel=channel_id,
            text=message,
            thread_ts=thread_ts
        )
        print(f"스레드에 댓글이 성공적으로 전송되었습니다: {response['ts']}")
    except SlackApiError as e:
        print(f"오류 발생: {e.response['error']}")

def get_bigquery(query):
    '''
    단순 빅쿼리 조회용
    '''
    try:
        result = bigquery_client.run_query_to_dataframe(query)
        print('Big query 조회 성공')
        # print(result)
        return result
    except Exception as e:
        print(f'Bigquery 오류 발생 {e}')

def send_bigquery_to_slack(user_id, query):
    data = get_bigquery(query)
    if data:
        for row in data:
            message = f'Bigquery 데이터: {row}'
            send_message_to_user(user_id,message)
    else:
        print('전송할 데이터가 없습니다.')


# 메뉴 기반 실행
if __name__ == "__main__":
    print("실행할 기능을 선택하세요:")
    print("1. 사용자에게 메시지 보내기")
    print("2. 스레드에 댓글 달기")
    print("3. Bigquery 데이터 조회")
    print("4. Bigquery 데이터 사용하여 사용자에게 전송")

    choice = input("번호를 입력하세요: ")

    if choice == "1":
        message = "안녕하세요! Slack Bot에서 보낸 메시지입니다."
        send_message_to_user(USER_ID, message)
    elif choice == "2":
        message = "스레드에 댓글 달기 테스트중입니다."
        send_reply_to_thread(CHANNEL_ID, THREAD_TS, message)
    elif choice == "3":
        query = 'SELECT * FROM geultto.geultto_10th.user_super_mart LIMIT 1;'
        get_bigquery(query)
    elif choice == "4":
        query = 'SELECT * FROM geultto.geultto_10th.user_super_mart LIMIT 1;'
        send_bigquery_to_slack(USER_ID, query)
    else:
        print("잘못된 입력입니다. 다시 실행해주세요.")

 

2.2.  사전 테스트 진행

최종적으로 우리가 보낼 메세지는 총 639명을 대상으로한 Direct Message이다. 사실 이정도의 많은 사람들에게 메세지를 보내는게 부담이여서 먼저 운영진 34명을 대상으로 설 일주일 전 테스트를 진행했다. 초안의 메세지는 다음과 같고 몇 가지 문제를 발견할 수 있었다.

모바일 기준 메시지, 확실히 개행이 빡빡하다.

 

  • 컨텐츠 문제
  1. 들여쓰기 내여쓰기가 들쭉 날쭉함
  2. 요약하여 쓸 수 있는 내용이 서술형으로 작성 됨

1번을 고민해보자면 슬랙은 멀티플랫폼이기 때문에 데스크톱, 모바일에서의 개행이 다르다는 점이다. 특히, 사람들에게 보내는 메세지가 처음 설계에서는 짧지 않았기 때문에 불릿포인트는 들여쓰기가, 줄글은 내어쓰기가 되어 가독성이 좋지 않았다.

2번은 불릿 포인트는 개조식, 그 밖은 서술형인 형식이 무너진 탓이였다. 되도록 불릿 포인트는 요약하여 작성하고 줄글은 문단마다 맨 뒤로 빼는 한편 "인용" Syntax를 이용해서 대화형임을 강조했다. CRM 메시지이기 때문에 사람마다 줄글이 미묘하게 다르기 때문에 이를 맞추려고 말 그대로 메세지를 2시간정도 "깍았다". 밑에 메세지가 최종본인데 나름 깔끔해졌다.(활동요약 개행이 좀 거슬린다.)

 

정리된 메시지 최종본

두 번째로는 테크적인 관점인데, 운영진 30명 정도에 보내는 정도야 별 문제 없었지만, 639명에게 보내게 되면 꽤나 골치아픈 경우의 수가 떠올랐다.

  1. slack api 의 초당 메세지 limit으로 인한 끊김
  2. 누구에게 어떤 메세지를 보냈다는 로그
  3. 너무 느린 속도..

1번은 API TOKEN 발급받은 동민님께 확인 받았다. 제일 좋은 tier라고 하셔서 문제는 없을 거라고 하셨고 나중에 확인해본결과 3 Tier 였다. 실제로 보내는 것도 문제가 없었긴 했는데 살짝 아슬한 정도가 아닐까 싶다. 다음에 체크해볼 것! 

https://api.slack.com/apis/rate-limits

 

Rate Limits

All good things in moderation: how rate limiting works throughout the Slack platform.

api.slack.com

 

2번은 혹여나 전송 시 에러가 날 수 있는 상황을 가정한다면, 누구까지 보냈는가가 중요했다. 그래야 받지 못한 사람부터 다시 시작할 수 있기 때문이다. 게다가 CRM 메세지 답게 모든 사람들이 다양한 경우의 수의 메시지를 받을 수 있기 때문에 확인 수단이 필요했다. 그래서 loop안에 받은사람, 메세지 내용, 해당 스레드의 ts식별자등을 저장한 csv파일을 로컬에 저장하는 로직을 추가했다. 

#코드 중 일부 
    #for문
	cnt += 1
    df_temp = pd.DataFrame({'name':name_list,'text':text_list,'ts':ts_list})    
    if cnt % 100 == 0:
        time.sleep(1)
        print(f'{cnt}명, {row[1]}까지 전달완료')
time_record = time.strftime("%Y%m%d_%H%M%S", time.localtime(time.time()))
pd.concat([df_sended,df_temp]).to_csv(f'./history/message_sended_{time_record}.csv', encoding = 'utf-8-sig')
print(f'최종 {cnt}명 전달완료')
end = time.time()
print(round(end - start), '초 소요됨')

3번은 사실 해결못한 부분이기도 했는데, 속도가 너-무 느렸다. 빅쿼리에서 데이터를 불러오는건 그렇다고 쳐도 가져온 데이터를 Pandas DataFrame을 .iterrow()로 접근하여 루프를 돌리는데  운영진 34명에게는 23초였으니 639명은 5분이 넘어갈께 뻔했다. 병렬식으로 보낼 수도 있을 것 같은데 굳이 지금 프로세스에서 바꾸고 싶지 않았다. 잘 작동하니까.. 🤣 그리고 이걸 깨달을 때 쯤이면 이미 보내기로한 due date 직전이였다. 

그정도까진 아니긴해 ~

 

결국 6분에 걸쳐 모든 사람들에게 메시지는 정상적으로 전송되었지만 후문으로는 왜 비동기식으로 하지 않았는가 에 대한 물음이 있었다고 한다 ㅋ_ㅎ (역시 개발자분들 ! 다음엔 그렇게 해볼게요) 사실 비동기식 처리 방법에 대해서 얼마안된 수민님의 글을 읽은 적이 있는데 사람은 망각의 동물이 맞나보다. 

어쨋든 돌아가잖아 한잔해

"테블리" 서비스의 핵심 기능 "개발"이라쓰고 "노가다"라고 읽는다 (1) - 데이터 수집(티스토리편)

 

"테블리" 서비스의 핵심 기능 "개발"이라쓰고 "노가다"라고 읽는다 (1) - 데이터 수집(티스토리편)

이번 포스트는 포텐데이 412에서 1등을 했던 테블리의 핵심 기능을 개발하며 겪었던 문제 및 트러블 슈팅을 다뤄보려고 한다.필요한 데이터 수집하기(티스토리)테블리는 이름 그대로 '테크 블로

suminii.tistory.com

 

또버지이신 은찬님도 슬랙봇 비동기 요청에 글을 쓰셨다! 마지막 CRM 메세지에 적용해보려고 한다.

https://daco2020.tistory.com/854

 

Python aiocache 로 비동기 Slack API 요청 캐싱하기

슬랙 API를 사용하면서 동일한 요청을 반복적으로 해야 할 때가 있습니다. 예를 들어 슬랙의 특정 메시지를 조회하여 데이터를 가져오는 경우가 있을 수 있죠. 하지만 슬랙 API는 사용량 제한이

daco2020.tistory.com

 

2.3. CRM 메시지 마무리

설 전날 결국 메시지를 보내 마무리 했다. 배포하는 날 기도하는 심정이 이런걸까? 금요일날 보내기 위해서 점심부터 계속 에러는 없을까 그 짤막한 코드를 보면서 계속 체크해봤다. 점점 쌓이는 지니봇의 DM.. 

6분은 좀 너무하긴 하네요

정확히 저녁 8시에 보내기 시작한 봇은 무리없이 6분간 순차 전송을 마무리하였고 사람들의 반응도 좋았어서, CRM팀으로서 뿌듯함을 느꼈다.

 

3. 갑자기 마인크래프트크요?

3.1. 마크 활동 시작

슬랙 봇에 이어지는 이야기인데, 서론은 이렇다. 갑자기 어떤분의 권유로 마인크래프트가 흥하기 시작했다. 그것도 설연휴에. 그렇게 정수님은 GCP을 가입하신 무료 크레딧 $400달러로 서버를 팠고 약 5 일차 정도 재밌게 진행했다. 근데 마인크래프트에서는 채팅기능에 더하여 사람이 접속하는 정보나 업적달성, 죽음 표시 등이 표기된다. 어 이거 데이터잖아? 하는 생각이 들었고 GCP 내부에 접근하면 LLM을 이용해서 인게임에서 마인크래프트 공략 등을 물어보는 세팅도 가능할 것 같아서 정수님께 문의 했다.  

그렇다 나는 말벌 아니 데이터 아저씨다..호다닥 🏃‍♂️

/Logs 파일을 확인해보니 날짜별로 분할되어있는 파일과 최신 log 2가지가 존재했다.  현재 기록들은 latest.log에 실시간으로 적히고 일정 용량이  차면 날짜별로 압축파일 .gz 파일로 저장되는 모양이였다. 

Logs 디렉토리의 파일들

여기서 재밌는 생각이 난게 채팅기록을 가지고 있으니 이걸 가지고 3-4일동안 진행된 정보를 요약하면 재밌겠다는 생각이 들었다. 게임채널이지만 시간이 나지 못해 참여 못한 사람들에게 안의 상황을 공유하면서 공감대를 키우고 유입 유도를 하는 한편, 기존 참여자들에겐 정보 요약을 통해 추억을 회상하는 넛지를 줄 수 있을 것 같아서 날짜별로 로그를 이용해서 LLM 에 태웠다.

초기 요약은 깔끔하다

여기서 재밌는 이슈가 있는데, 채팅파일이 워낙 길다보니까 무료버전의 ChatGPT, Claude, 뤼튼(OPEN AI 기반이긴 하지만)에서 모두 텍스트 Length 초과로 거절당했다... 반면 Perplexity 와 Gemini는 토큰이 넉넉한지 요약은 해주더라. 결국 Gemini에게 요약하는 형식을 가지고 3일차 정도 밀어 넣어봤는데 Context Length 이슈로 3일차 정도 뒤에는 자기 멋대로 요약을 하기 시작했다... 아놔. 결국 마음편하게 ChatGPT를 다시 구독했다.

Context가 길어진 후, 응답 구조가 변경되었다.
반응 감사해요 ~

좀 더 프롬프트를 깍고 일정하게 형식을 few shot해놓으면 퀄리티가 좋아지겠지만 그렇게 까지 시간을 쓰고 싶지 않았고 날짜도 5일차 정도 밖에 되지 않았기 때문에 간단하게 정리만 했다.

3.2 접속 알림 봇을 만들까?

어쩌다보니 마인크래프트 GCP 서버 관리까지 참여하고 있어서, 이 때를 틈타 CRM에서 했던 연장으로 간단하게 출석 체크 봇을 만들고 싶어졌다. 사실 생존게임이라는게 혼자하기보다는 누구랑 같이 할때 제일 재밌는거라 함께 하는게 젤 중요하다 생각했다. (그리고 2주정도 하다 말꺼니까)   그러던 중 문득 생각난 것이, 마인크래프트가 꽤나 API에 적극적이라, 실제로 인터넷 방송에서는 도네이션을 연결해서 스트리머에게 필요한 물품을 준다던가 갑자기 점프를 시킨다더가 즉사를 시킨다던가 하는 식으로 재미를 더하고 있었던 장면이 생각났다.

https://www.youtube.com/watch?v=bgA6BCSSQaY

 

그래서 간단하게 먼저 출입 봇을 만들어서 누가 활동하고 있는지 등에 대한 정보를 전달해주면 그걸 보고 다른사람이 함께 한다던지에 대한 넛지를 줄 수 있을 거란 생각이 들었다. GPT 한테 물어봤다.

역시 GOD - PT

이를 바탕으로 요약한 절차는 다음과 같다.

  1. 먼저 서버의 설정에서 서버의 상태(접속인원 등)을 확인할 수 있는 설정과 포트를 열고
  2. Python을 이용해서 해당 정보를 가져오고
  3. 슬랙 봇을 이용해서 스레드에 쏴주면 된다.

3번은 CRM에서 진행했던 거라 아주 기억이 생생할... 줄알았지만 Github에 머지해놓은것 있으니까 이걸 참고하고 1번 마크 서버의 정보를 가져올 수 있는 mcstatus 모듈이 당연히 있더라 

https://pypi.org/project/mcstatus/

 

mcstatus

A library to query Minecraft Servers for their status and capabilities.

pypi.org

위 내용을 조합해서 2번을 만들면 아주 쉽게 다음과 같이 만들 수 있다. 마인크래프트와 파이썬 스크립트의 위치는 동일한 GCP 이기 때문에 Localhost를 이용해서 어렵지 않게 서버정보를 불러올 수 있다. 이렇게 정보를 가져올 수 있다는걸 확인했으니, 이제 슬랙 봇 api를 이용해서 보내기만 하면된다.

import os
import time
import requests
import json
from mcstatus import JavaServer

# 🛠 환경변수에서 Slack Bot Token 불러오기
SLACK_BOT_TOKEN = os.getenv("SLACK_BOT_TOKEN")
if not SLACK_BOT_TOKEN:
    raise ValueError("❌ 환경변수 SLACK_BOT_TOKEN이 설정되지 않았습니다.")

# 🛠 Slack 설정
CHANNEL_ID = "C01XXXXXX"
THREAD_TS = "000000.00000"  # 기존 메시지 thread_ts 값

# 🛠 마인크래프트 서버 설정
SERVER_IP = "127.0.0.1"
QUERY_PORT = 25565
server = JavaServer.lookup(f"{SERVER_IP}:{QUERY_PORT}")

# 🛠 ID → 이름 매핑 (등록되지 않은 경우 ID 그대로 출력)
player_name_map = {
 "ID" : "이름"
}

# 🔄 이전 접속자 목록 저장
previous_players = set()

# 📌 Slack 스레드에 댓글 남기기
def send_slack_reply(message):
    url = "https://slack.com/api/chat.postMessage"
    headers = {"Authorization": f"Bearer {SLACK_BOT_TOKEN}", "Content-Type": "application/json"}

    data = {
        "channel": CHANNEL_ID,
        "text": message,
        "thread_ts": THREAD_TS  # 기존 메시지의 스레드에 댓글 작성
    }

    response = requests.post(url, headers=headers, data=json.dumps(data))
    response_data = response.json()

    if response_data["ok"]:
        print(f"✅ Slack 스레드에 댓글 작성 완료: {message}")
    else:
        print("❌ Slack 댓글 작성 실패:", response_data)

# 📌 서버 인원 체크 루프 실행
def monitor_minecraft_server():
    global previous_players

    print("🔄 마인크래프트 서버 접속 감지 시작...")

    while True:
        try:
            # 서버 상태 가져오기
            status = server.status()
            current_players = set(player.name for player in status.players.sample) if status.players.sample else set()

            # 🔹 새로운 플레이어 입장 확인
            new_players = current_players - previous_players
            left_players = previous_players - current_players

            message_list = []

            # ✅ 여러 명이 동시에 입장하면 한 번에 메시지 출력
            if new_players:
                joined_names = [player_name_map.get(player, player) for player in new_players]
                joined_message = f"🎉 {', '.join(joined_names)} 님이 입장했습니다!"
                message_list.append(joined_message)

            # ✅ 여러 명이 동시에 퇴장하면 한 번에 메시지 출력
            if left_players:
                left_names = [player_name_map.get(player, player) for player in left_players]
                left_message = f"👋 {', '.join(left_names)} 님이 퇴장했습니다!"
                message_list.append(left_message)

            # ✅ 현재 인원도 한 번만 출력
            if message_list:
                total_players = len(current_players)
                message_list.append(f"🔹 현재 접속 인원: {total_players}명")
                send_slack_reply("\n".join(message_list))

            # 🔹 접속자 목록 업데이트
            previous_players = current_players

        except Exception as e:
            print(f"❌ 서버 정보를 가져오는 중 오류 발생: {e}")

        time.sleep(5)

# 🔥 실행
if __name__ == "__main__":
    monitor_minecraft_server()

 

위 코드를 실행하여도 GCP SSH가 닫기면 떨어지기 때문에 screen을 이용해서 백그라운드에서도 돌게 만들었다.

# screen 생성
screen -S mc_slack_bot
# 파이썬 스크립트 실행
source /가상환경명/bin/activate
python monitor_sendmsg.py
# CTL + A , D를 통해 screen 빠져나오기

#스크린 확인(사용자 디렉토리 귀속)
screen -ls

완성!



4. 마무리

Slack API가 잘 되어있고 GCP같은 클라우드 환경도 언제든지 쉽게 접속할 수 있는 환경이라 쉽게 배웠다. 더불어 옆에 있는 개발자분들에게 물어가면서 배워가는 점도 한 몫하고, 아이디어만 있다면 ChatGPT 를 이용해서 코드 구현부분을 외주 줄 수 있다는 점도 좋은 것 같다. 최적화나 설계 부분에 대해서는 여전히 CS적인 지식이 필요하지만 짧게 짧게 Prototype을 만들고 구현하는 경험이 재미있었다. 예전에는 프로그램을 만들기 위해서 가능한 기능성이 높은 구조를 높게 잡고 하다보니 지치거나 포기하는 경우가 많았는데 이제 경험이 쌓이다보니 작은 단위로 Divide(&Validation) and Conquer 를 하는 개발도 익숙해진 것 같다. 남은 글또 기간이나 내 일에서도 SaaS 활용과 엔지니어링을 지속적으로 키워나가면 확실한 무기가 될 수 있을 것 같다.

그리고 재밌고 유용하잖아 한잔해 🍻

예상독자

  • 데이터를 주기적으로 수집하는 자동화를 구현하고 싶은 데이터 엔지니어, 분석가, 개발자

목차

  1. 글의 개요
  2. GCP 셋업
    1. GCP 인스턴스 만들기
    2. 빅쿼리 연결
  3. 자동화 익히기
    1. Linux 스케쥴러 Cron 알아보기
    2. Hello world 출력하는 실행파일 만들기
  4. OPEN API 데이터 빅쿼리에 저장 자동화하기
    1. 깃 설치 및 환경설정
    2. 파이썬 가상환경 설정 및 패키지 설치
    3. 스크립트 실행 및 자동화 태우기

1. 글 개요

데이터 수집부터 시작하고자하는 데이터 직무라면 파이프라인에 대해서 고민하게 될 것이다. 다양한 방법이 있겠지만 나는 그렇게 부지런한편이 아니니 가장 간단하게 자동화하고 싶은 마음에 GCP을 사용하기로 했다. 

가장 무난하게 할 수 있는 데이터 파이프라인

 

본 글에서는 GCP 서버를 세팅하여(자동화), kamis API에서 데이터를 주기적으로 데이터를 수집하고(수집, 전처리), 이를 데이터베이스에 저장하고(적재) 시각화하여, 제철과일을 싸게 먹겠다(액션 플랜) 라는 목적으로 시작된 사이드 프로젝트이다.  이를 위한 빌드업은 다음 글에 작성하였다. 

 

[분석] 마트에 있는 딸기 얼마가 합리적인 가격일까?

1. 아이디어 흔히 봄에는 딸기철이라고한다. 나와 아내는 봄에 딸기가 출하되면 항상 냉장고에 딸기를 상시...

blog.naver.com

 

 

GCP 셋업를 위한 기본 지식: 하드웨어와 OS 개념

이글을 쓰게된 이유 부트캠프에서 자동화 강의를 준비하다보니 생각보다 컴퓨터 기본 하드웨어 지식이 부족한 사람들이 많다. 컴퓨터 내에 덕지덕지 RAM을 차지하고 있는 프로그램이 있는지 모

snowgot.tistory.com

 

2. GCP 셋업

✅ GCP 인스턴스 만들기

Google Cloud Console -> Compute Engine에 들어간다. 

 

상단의 인스턴스 만들기를 클릭한다. 여기서 인스턴스란 컴퓨터라고 생각하면 된다. 

 

이때 3분할의 화면이 보이는데

  • 좌측: 처음부터 셋팅할 것인지 템플릿을 가지고 만들지 선택할 수 있다. 
  • 우측: 현재 기본 세팅으로 했을 때 나오는 예상 가격이다. 당연하지만 성능이 좋아질수록 가격이 올라간다. 

    구글로부터 컴퓨터를 "대여" 하는 것이기 때문에 빌리는 것만으로도 비용이 나온다. 마치 집에 있지 않아도 나가는 우리집 관리비 처럼 ~_~

  • 중앙: 인스턴스를 셋업하기 위한 상세 설정이다. 사양 등 세부 설정을 할 수 있다.

 

인스턴스 생성 형태

 

예상 가격

 

인스턴스 세부 설정에서 확인할만한 사항은 다음과 같다. 

  • 리전(Regions) : Google Cloud Platform 서비스들이 제공되는 서버의 물리적인 위치
  • 영역(Zones) : 리전(Regions) 내 Google Cloud Platform 리소스의 배포 영역

리전과 영역 설정에 따라 예상가격이 달라진다. 서울권을 설정하면 Latency와 같은 부분이 더 좋아질 수 있다고는 하나 공급이 다른 지역보다 적기 때문에 가격이 올라간다. 우리의 경우 Latency가 그렇게 큰 부분은 아니기 때문에 기본지역으로 설정했다.  

인스턴스 세부 설정 & 리전

  • 머신 구성: 인스턴스 자원 변경 가능
  • 부팅디스크: 디스크와 운영체제(리눅스 배포판)을 선택 가능

머신 구성
부팅 디스크

이후 방화벽에서 HTTP 트래픽 허용을 누르고 만들기를 클릭하면 수분 내 인스턴스가 생성된다.

방화벽 설정 & 인스턴스 생성

 

생성이 완료되면 다음과 같이 뜨며, 내부IP, 외부IP와 연결 부분이 생성된다. 내부IP는 말그대로 내부 네트워크(Google 서버내)에서만 접근 가능 하기 때문에 보안이 우수하다. 또한 지연속도가 적고(속도가 빠름) 비용이 저렴한 특징을 가지고 있다. (외부 인터넷을 사용하지 않아도 되니)한 장점이 있습니다. 외부 IP는 우리가 사용하는 인터넷 IP이다. 

 

인스턴스 생성 완료

 

위해서 연결부분의 점 세개를 누르면 브라우저 창에서 열기를 통해 접속이 가능하다. 

초기 인스턴스 화면

 

무료 크레딧으로 인해 과금은 안되지만 추가 비용을 막거나 인스턴스를 내리고 싶다면 같은 방법으로 중지/정지/삭제가  가능하다. 

 

  • 중지: 일시정지에 해당하며 OS, 메모리를 그대로 유지하는 상태, 60일 이후 정지로 변경
  • 정지: 인스턴스가 종료되며 OS를 종료하게 된다. 

더 자세한 내용은 다음 글을 참고해 볼 수 있다.

 

VM 일시정지 및 재개  |  Compute Engine 문서  |  Google Cloud

의견 보내기 VM 일시정지 및 재개 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Linux Windows 이·문서에서는·가상·머신(VM) 인스턴스를 일시정지·및 재개하

cloud.google.com

 

[GCE] VM이 중지 / 일시중지 상태일 때 각각 청구되는 비용은?

안녕하세요, 베스핀글로벌 GCP Support팀입니다. 이번 아티클에서는 주제로 "GCE VM의 상태별 청구되는 비용"을 다루고자 합니다. GCE VM의 상태별 청구 비용 GCE VM의 실행이 필요하지 않을 때, 비용을

support.bespinglobal.com

 

기본적으로 GCP 인스턴스를 생성하면 Python은 깔려있다. 그 외에 필요한 유틸리티는 apt를 통해 직접 설치해야한다. 

python3 --version

 

기본적인 리소스 확인도 가능하다. top 유틸리티는 조금 못생겨서, htop을 설치한다.

#기본 자원관리자
top


#htop 설치
sudo apt-get install htop
htop

 

✅ 빅쿼리 연결

같은 Google 제품이다보니 gcloud 명령어가 설정되어있고 손쉽게 bigquery를 연동할 수 있다. 

gcloud auth login
#이후 나오는 링크를 통해 인증

#현재 기본프로젝트가 설정되어있지 않다면 
#현재 계정에 있는 프로젝트 리스트 확인
gcloud projects list
#프로젝트 선택
gcloud config set project <프로젝트명>

나는 kamis.str 데이터를 이미 빅쿼리에 올려놓아서 설정하여 볼 수 있다. 

#bq명령어는 빅쿼리를 볼때 사용하는 명렁어

#현재 데이터 셋 확인
bq ls

#현재 데이터셋의 테이블 명확인
# bq ls <데이터셋 명>
bq ls kamis

#SQL 쿼리날리기
# bq query 'SQL 명령어'
bq query --use_legacy_sql=false 'SELECT * FROM `kamis.str`'

 

3. 자동화 익히기 

✅ Linux 스케줄러 Cron 알아보기

gcp 설정을 마쳤다면 Linux 의 스케쥴러인 Cron을 익혀볼 차례다. Cron이란 스케쥴링을 담당하는 Linux 유틸리티로, Crontab은 Cron table의 약자로, 실행할 명령어를 담고있는 파일이다.

  • Cron 주기
    • * * * * * {실행 명령}
    • 순서대로 분(0-59), 시(0-23), 일(0-31), 월(21), 요일(0-6)
    • 요일은 0(일), 1(월)… 6(토)
    • *은 모든 이라는 뜻이므로
      • 예시
        • * * * * * {실행명령} : 매 분 마다 실행
        • */10 * * * * {실행명령} : 매 10분 마다 실행
        • 0 12 * * * {실행명령} : 매일 정오 12시에 실행
  • crontab 관련 명령어
    • which crontab : crontab 위치경로 확인
    • crontab -e : 편집
    • crontab -l : 작업 내용 확인
    • crontab -r : 전체 작업 삭제

Cron 스스로 작동하는게 아니고 sytemctl 이라는 리눅스 서비스 관리프로그램으로 시작과 종료를 시킨다. 따라서 다음 명령어를 사용해야한다. 관리자 권한 sudo 가 필요하다. 또한 Crontab 설정을 변경할 때마다 재시작 해줘야 한다.

    • systemctl restart cron: cron 재시작
    • systemctl start cron : cron 시작
    • systemctl stop cron : 중지
    • systemctl status cron : 작동확인

✅ Hello world 출력하는 실행파일 만들기 

Linux에서 문자를 출력하는 쉘파일(.sh)를 만드는 것은  nano 편집기를 이용해서 .sh 실행파일에서의 출력문인 echo 를 사용하면된다. 

#nano 편집기로 만들기
#이름을 정하면 알아서 해당 이름의 파일을 생성

nano hello.sh

#hello.sh 파일에서 다음 내용을 입력
echo "Hello World!"

이후 CTL + X  -> Y를 이용해서 nano 편집기 저장 후 나오기를 하면 된다. 이후 .sh 파일을 실행하는 것은 sh 명령어를 사용하면 된다.

sh hello.sh

 

✅ 매분마다 출력하는 실행파일 만들기 

sh 파일을 만들었으니 이를 cron 에 태워서 주기적으로 실행하게 만들면 된다. 이때 설정해야하는 것은 (1) crontab에 해당 sh파일 위치 알려주기 (2) systemlctl을 이용해서 crontab 실행해 주는 것이다.

# 현재 경로확인
pwd

# crontab 편집
crontab -e 

# crontab에 매분 해당 파일을 실행하도록 입력
# 동시에 출력을 .log 파일로 보내고 
# 2>&1 구문에 따라 오류 메세지도 동일한 파일로 보냄
* * * * * /home/jayjunglim/hello.sh >> /home/jayjunglim/hello.log 2>&1

# crontab 나와서 현재 변경내역 확인
crontab -l 

#설정한 내용 저장하기 위하여 재부팅
sudo systemctl restart cron

# cron 시작하기
sudo systemctl start cron

# 상태확인
sudo systemctl status cron

cron이 실행되고 있음을 확인

이후 로그 파일을 열어보면 잘 작성됨을 확인할 수 있다.

# 로그 확인
nano hello.log

# cron 종료
sudo systemctl stop cron

 

4. OPEN API 데이터 빅쿼리에 저장 자동화하기 

이제 이모든 것을 합쳐 OPEN API 로부터 데이터를 가져오고 이를 빅쿼리에 저장할 것이다. 또한 이를 자동화를 태울 것이다. 엄격히 말하면 매일 자동화를 시켜서 누적되는 데이터를 보아야겠지만, 데이터 삭제 & 새로 생성 하는식으로 자동화를 테스트해볼 생각이다. 

이전에 환경세팅해줄 것이 몇가지 있다. 먼저 git을 설치하여 github 에 있는 코드 뭉치를 가져올 것이며, python 가상환경 설치를 해야한다. 

✅ 깃 설치 및 환경설정

  • 깃 설치
# 깃 설치
sudo apt-get install git

#폴더생성
mkdir wheresisplanb

#리포지토리 파일 복사
git clone https://github.com/bellepoque7/whereisplanb.git

#(선택) 만약 repository를 이미 clone하였고,  업데이트 사항이 있다면
git pull https://github.com/bellepoque7/whereisplanb.git

해당 리포지토리에서는 데이터를 불러오고 전처리하는 web_api_kamis.py  파일과  이를 빅쿼리에 kamis2bq.py 적재하는 파일을 사용할 예정이다.  만약 빅쿼리가 어색하고 파이썬으로 데이터 적재하는 법을 알고 싶다면 다음 글을 참고한다. 

 

[글또] Google bigquery에 데이터를 적재하고 태블로로 데이터 가져오기

예상독자 로컬 머신와 빅쿼리를 연결해보고 싶은 독자 데이터 적재 자동화를 동해 태블로에 시각화하고 싶은 독자 1. 배경 데이터분석을 넘어 데이터 파이프라인을 만들어 자동화를 하는 경우

snowgot.tistory.com

 

web_api_kamis 는 농수산물 데이터(kamis)를 가져오기 위한 데이터이며, 이를 빅쿼리에 저장하기 위해서 kamis2bq.py 스크립트를 따로 만들었다. 해당 스크립트는 딸기 데이터에 대하여 데이터를 가져오고 빅쿼리에 데이터가 있다면 삭제하고 업로드 하는식으로 최신화 하는 형태이다. 

제일 좋은 건 계속 누적해서 데이터를 수집하는 것이지만 일단 prototype을 만들고 추가 적재를 하기위해서 코드을 추가하기로 하였다. 

  • kamis2bq.py 스크립트
#GCP debain 환경에서 실행되는 코드

import pandas as pd
from web_api_kamis import *
from google.cloud import bigquery
from google.cloud.exceptions import NotFound
import time

# df_cd = pd.read_csv('./Data/category_detail_code.csv', encoding = 'euc-kr')
#데이터 불러오기 및 전처리
start = time.time()
df_str = kamis_api_2(itemcode ='226')
df_str = df_str.assign(date = lambda x : df_str['연도'] + '/' + df_str['날짜'])
df_str['date'] = pd.to_datetime(df_str['date'], format = 'ISO8601')
df_str = df_str.assign(price_100g = lambda x: x['가격'].str.replace(',','').str.replace('-','0').astype('int')/20)
df_str.columns = ['itemcode','kindcode','countrycode','market','year','origin_date','tot_price','date','price_100g']
print('1/4 데이터 전처리 완료')

#빅쿼리 연결 설정
project_id = 'challenge-a-418407'
client = bigquery.Client(project=project_id)
dataset_id = 'kamis'
table_id = 'str'
full_dataset_id = "{}.{}".format(client.project, dataset_id)
print('2/4 빅쿼리 연결 설정 완료')

#스키마 설정(빅쿼리에 올라갈 데이터 형태)
schema = [
    bigquery.SchemaField("itemcode", "STRING"),
    bigquery.SchemaField("kindcode", "STRING", mode="NULLABLE"),  # NULL 값을 허용
    bigquery.SchemaField("countrycode", "STRING"),
    bigquery.SchemaField("market", "STRING", mode="NULLABLE"),  # NULL 값을 허용
    bigquery.SchemaField("year", "STRING"),  # 연도가 숫자로만 구성되어 있더라도, 여기서는 문자열로 처리
    bigquery.SchemaField("origin_date", "STRING"),  # 날짜를 문자열로 처리할 수 있지만, DATE 타입을 사용하는 것이 더 적합할 수 있음
    bigquery.SchemaField("tot_price", "STRING"),       
    bigquery.SchemaField("date", "DATE"),  # datetime64[ns] 타입은 BigQuery의 DATE 타입으로 매핑
    bigquery.SchemaField("price_100g", "FLOAT64"),  # float64 타입은 BigQuery의 FLOAT64 타입으로 매핑
]
job_config = bigquery.LoadJobConfig(schema=schema) 

dataset_ref = client.dataset(dataset_id)
# 데이터셋이 없을 경우, 데이터셋 생성
try:
    client.get_dataset(dataset_ref)
    print("{} 데이터셋 이미 존재".format(full_dataset_id))
except NotFound:
    dataset = bigquery.Dataset(full_dataset_id)
    dataset.location = "asia-northeast3"
    client.create_dataset(dataset)
    print("{} 데이터셋 생성".format(full_dataset_id))

print('3/4 dataset 생성체크 완료')

# 테이블 존재 여부 확인
table_ref = client.dataset(dataset_id).table(table_id)
try:
    client.get_table(table_ref)
    client.delete_table(table_ref)
    print("{} 테이블이 존재하여 삭제.".format(table_id))
except NotFound:
    print("{} 테이블이 없어 생성필요".format(table_id))

job = client.load_table_from_dataframe(df_str, table_ref, job_config = job_config)
job.result()
end = time.time()
print("4/4 작업완료, {}초 소요".format(round(end-start)))

 

또한, cert_info.py을 생성해야하는데 이 부분은 kamis api를 사용하기 위한 인증키 파일이다. 

  • cert_info.py
# 개인키와 이메일은 비공개
# return '' 본인들 key 랑 id 넣으시면 됩니다.
def cert_key():
    return '본인 key'

def cert_id():
    return '본인 id'

 

이후 pip 패키지 관리자를 설치한다.

sudo apt install python3-pip

#pip 확인하기
pip --version

 

✅ 파이썬 가상환경 설정 및 패키지 설치

Debian Linux에서는 글로벌 파이썬 패키지 설치가 불가능하고 권장되지 않으므로 가상환경을 설정한다.

sudo apt install python3.11-venv
#가상환경 생성(이름:env11)

python3 -m venv env11

#가상환경 실행
source env11/bin/activate

# 가상환경 종료
# deactivate

#설치된 리스트 보기
pip3 list

# 가상환경 활성화 되어있을 때
# Python 모듈 통합설치
pip3 install -r requirements.txt

#혹은 개별 설치
pip3 install pandas requests matplotlib pyarrow pandas-gbq

#google모듈 import 에러 해결 
pip3 install --upgrade google-api-python-client
pip3 install google-cloud-bigquery

#설치확인
pip3 list

위 설정이 모두 마무리가 되었다면 준비는 끝났다. 

 

✅ 스크립트 실행 및 자동화 태우기

python3 kamis2bq.py

위와 같이 뜨면 성공.

빅쿼리스튜디오에서도 테이블 세부정보를 보면 잘 작동했음을 확인할 수 있다. 

Big query Studio 화면

 

자동화를 태우고 싶으면 crontab 파일을 설정해주면 되며, 이때 중요한 점은  가상환경으로 설정했기 때문에 가상환경 경로의 Python 디렉토리를 구체적으로 작성해주어야 정상적으로 실행된다.

#cron 접속
crontab -e
# 다음 텍스트 삽입 
# (매일 정오 마다) / (가상환경의 파이썬3으로) 
# (다음 경로의 파일을 실행해라) / (로그를 남겨라) 
0 12 * * * /home/jayjunglim/env11/bin/python3 /home/jayjunglim/whereisplanb/kamis_api/kamis2bq.py  >> /home/jayjunlimg/kamis.log 2>&1

# 설정변경했으니 재시작
sudo systemctl restart cron

# 스케쥴링 시작
sudo systemctl start cron

정상적으로 실행되는 모습

 

이후 대시보드에 연결해서 100g당 도매 가격현황을 볼 수 있다.다음은 딸기의 가격이다. 도매 기준으로 100g당 1000원에 정착하고 있으므로 마트 구매에 참고하시라. 요즘 1000원대로 많이 내려온 걸 실제로 확인할 수 있었다.


이렇게 해서 미루고 미뤘던 데이터 적재 자동화 프로토타입 만들기까지 완성했다. 작년초부터 만들어야지 하면서 미뤘던 건데, Linux에 익숙하지도 못했어서 삽질을 많이 했는데 이번에는 반나절정도에 해결할 수 있어서 매우 뿌듯하다. 이제 해야할일은 주기적으로 데이터 적재해서 현재 적절한 가격을 볼 수있게 만드는 것!  아무래도 간단하게 steamlit으로 구현하면 되지 않을까 싶다. 

 

7. 글또 9기 글 모음

이글을 쓰게된 이유

부트캠프에서 자동화 강의를 준비하다보니 생각보다 컴퓨터 기본 하드웨어 지식이 부족한 사람들이 많다. 컴퓨터 내에 덕지덕지 RAM을 차지하고 있는 프로그램이 있는지 모르고 쓴다던지... 컴퓨터를 어떤 사양의 기준으로 사야할지 등등  그래서 GCP 서버 구축하는 법을 알려줄 겸 기본적인 하드웨어 지식과 함께 강의한 내용을 정리 해보려고 한다. 

 

예상독자

  • Computer Science 지식이 전무한사람

 

1. 운영체제란

회사를 입사하게 된다면 IT직군에 한하여 맥북 vs 윈도우 노트북의 양자택일 선택지가 주어진다. (물론 선택지가 없는 경우도 많지만). 사실 이 경우 운영체제가 가장 중요한 기준이 될 것이다. 근데 운영체제가 뭐냐고 한다면.. 

  • 운영체제(OS, Operating System): 컴퓨터 하드웨어의 리소스를 관리해주면서 동시에 여러 어플리케이션이 작동할 수 있는 환경을 제공해주는 소프트웨어. 개인용 데스크탑에서는 Windows를 가장 많이쓰고, Linux, Mac 등이  존재

운영체제 종류

아마 우리에게는 두가지 선택지가 있으니 이 부분을 기술 하자면 다음과 같다.

  • Windows
    • 어릴때부터 학습된 운영체제
    • 그래픽인터페이스(GUI)의 편리함
    • 제어판, 탐색기 등 대부분의 기본 프로그램에 익숙
  • MacOS
    • 애플에서 개발한 운영체제
    • 기본적으로 GUI는 이질적이지 않으나 후술할 Linux기반 파일 디렉토리 때문에 분석가들 환경 설정의 어려움
    • 프로그래밍 or 디자인 기능 특화

그래서 무엇을 쓸까? 를 고민한다면 거의 모든 대부분의 경우에는윈도우를 추천하는 편이다. 이유는 1. 따로 운영체제에 대한 학습이 필요하지 않으며 2. MS가 제공하는 3신기(Docs, Excel PPT) 활용도가 좋기 때문이다. 

반면 맥북이 갖는 장점은 1. 이쁘다 간지난다. 2. Linux 계열 컴퓨터 언어를 조금 이해할 수 있다.(강제로 학습하게 된다)3. 운영체제 관리가 잘되어서 적은 램으로도 잘 돌아간다는 점이다. 

사실 나는 위 장점/단점을 고른다기보다는 단 하나의 맥북의 단점으로 고르게 되었는데 그것은 맥북에서 편집한 파일의 윈도우에서 자-모음 분리현상 때문이다. 정말 사소하지만 메일로 일하는 나에겐 너무 프로페셔널 해보지 않아서 윈도우로 갈아타는 계기가 되었다. 물론 Google Drive 업로드 후 첨부 등 우회방법이 있지만 굳이 그걸 신경쓸만큼 맥북이 이득이 없다라고 판단했다. 

 

본인이 분석가면 대부분의 경우의 윈도우가 정답이다. 

 

2. 하드웨어 

✅ 컴퓨터 하드웨어 설명

 

예산이 되어있는 개인 구입일 때는 비용도 제한하게 되는데, 이때 고민하는 것이 하드웨어 사양이다. 사실 서버를 돌릴 때도 간단한 작업은 비싼 장비는 필요 없을 뿐더러 비용만 더 많이 나가기 때문에 적당한 걸 선택한다. 보통은 무료 tier에 가까운걸로..?

간단 정리하자면 CPU, RAM, 디스크가 있고 요즘은 딥러닝 혹은 그래픽 작업에 의해서 GPU까지 얘기하지만 서버를 대여할때는 딥러닝까지 한다면 빌려야겠지만 비용이 상당하기 때문에 개인 입장에서는 GPU까지 고민하면서 서버를 쓸 정도면 이미 이 부분은 잘 알거라 생각한다.

  • CPU(Central Proceesing Unit)은 중앙처리장치로 모든 연산을 담당
  • 메모리일시 기억을 담당
    • 데이터 분석 툴은 대부분 램에 데이터를 담기 떄문에 클수록 선호
    • 재부팅하면 날라감
    • 혹은 프로그램을 끄면 날라감(왜? 메모리는 모든 프로그램에서 써야하니까 방빼!)
  • 저장공간(HDD, SSD)는 영구 기억을 담당
    • 역시 클수록 좋습니다.
  • 그래픽카드는 부동소수점 연산을 담당
    • 쉽게 말하면 그래픽을 담당
    • 최근에는 딥러닝 수행에 아주아주 큰 역할
    • 대부분의 데이터분석은 그래픽카드는 필수 요건은 X

여기서 CPURAM을 묶어서 컴퓨터 자원이라고 명명한다. 대부분 분석가들은 최적화는 신경 잘 안쓰고 컴퓨팅 파워에 맡기나 별로 좋은습관은 아니다. 특히  대용량 데이터를 다룰때는 굉장히 중요해진다.

✅ 컴퓨터 사양 확인 방법

위 내용을 현실적으로 이해하려면 본인의 컴퓨터 사양을 확인해보면 될 것이다. 

윈도우의 경우는

  • 기본 사양: window 키 + R → dxidag 입력

  • 컴퓨터 자원확인: CTL + SHIFT + ESC

 

맥북의 경우는

  • 기본사양: 왼쪽 상단 애플마트 - 이 Mac에 관하여

  • 컴퓨터 자원 확인: Command + space -> 활성 상태 보기

 

3. Linux

GCP를 셋업하기로 했으면 제3의 운영체제 Linux에 대해서 알아야한다. 사실 모든 운영체제의 조상 UNIX으로부터 태어난 운영체제이기 때문에 우리가 알게 모르게 사용하는 컴퓨터 자원은 거의 Linux로 구동된다. 개인용 컴퓨터 PC가 아무래도 현실에 자주 마주하는 컴퓨터다 보니까 입문자들이 이런 부분을 모르는게 대부분이다. 

✅ Linux란 ?

 

Linux는 1991년 Linus Torvals가 Unix 운영체제 기반으로 개발한 무료소프트웨어 운영체제로 현재 전세계적으로 300여가지의 배포판이 존재하여, 사용자에 따라 결정할 수 있는 폭이 넓음 (ex 개인용컴퓨터, 모바일. 여러분들이 사용하는 PC 외에 대부분 SaaS 서비스가 리눅스 기반 (Colab도!) 배포판 분류로 회사에서 관리하는 레드햇, 우분투 커뮤니티 관리하는 데비안, 젠투, 페도라가 있다.

✅ Linux 용어 정리 

커널(Kerenel)은 운영체제의 핵심으로 메모리관리, 프로세스 관리, 장치 관리 등 컴퓨터의 모든 자원을 제어하는 유틸리티이다. 또한, 디렉토리(Directory)는 윈도우로 따지자면 파일 구조(탐색기)이며 파일 시스템에 의해 관리 되고 있다. 아무래도 리눅스를 처음 접할 때 파일 구조가 달라서 많이 혼동하곤 한다. 사실 윈도우에 파일시스템도 샅샅이 아는 사람은 드물 것...

경로별 간단 설명을 하자면

  • / : 파일 시스템의 가장 최상단 디렉토리(Root 디렉토리)
  • /bin: 리눅스의 기본적인 명령어가 저장된 디렉토리
  • /home: 일반 사용자의 홈 디렉토리(자주 접근)
  • /boot: 운영체제 구동시 부팅 설정 파일이 존재
  • /usr : 일반 사용자를 위한 프로그램 파일(윈도우의 Program files)
    • /usr/bin: 일반 사용자들이 사용가능한 명령어 파일이 존재하는 디렉토리
    • /usr/local: 새로운 프로그램이 설치되는 공간

이때 디렉토리 경로를 표현하는 방식은 2가지가 있다.

  • 절대경로
    • 이름 그대로 절대적인 경로
    • Root 부터 시작하는 경로를 지정하며 어느 파일에 넣든 정확한 경로를 전달함
    •  예시
      • (ex 경기도 관양시 관평로 OOO번길 OO, 101-202) → 어디서 찍든 동일한 집임
  • 상대 경로
    • 현재 내 위치 기반 상대 경로
    • . : 현재 디렉토리
    • .. : 상위 디렉토리
    • 예시
      • 우리집 윗층 → 우리집이 어디냐에 따라 윗층이 달라짐

✅ 쉘(Shell) 

쉘은 Linux 사용하기 위한 인터페이스이며 기본적으로 키보드만 사용 가능하다. 마우스와 같은 UI가 익숙한 사람들 여기서 숨이 턱막힌다. 쉘은 사용자가 입력한 명령어를 실행하여 다른 프로그램이나 커널로 전송하는 기능을 하는 유틸리티이며, 사용자와 커널의 중간다리로 리눅스는 Bash 를 기본으로 사용한다. 분석가 기준으로 대표적인 명령어로 pip(파이썬 패키지 관리자)가 있다. Python 패키지를 설치하는 리눅스 명령어이자 유틸리티 이름이 pip인 것이다. 윈도우에서는 명령프롬프트(Command)가 있으며 명령어는 다르다. 반면 Linux Shell 명령어와 유사한 Powershell 이 기본으로 탑재되어 있다. 

맥의 Shell인 terminal

리눅스에서 자주 쓰는 단축키 & 명령어

  • 화살표 위아래: 이전 실행 명령어 확인
  • Tab키: 자동완성
  • top : 현재 리소스 확인하기
    • htop : 더 이쁘게 리소스 확인하기
    • 단 htop은 기본프로그램이 아니기 때문에 설치해줘야합니다.
    • sudo apt install htop : sudo(강제로) apt(리눅스 패키지 관리자), insatll(설치), htop(프로그램)
  • nano {텍스트파일} : Linux에서의 파일 편집기(a.k.a 메모장 기능)
  • pwd: print working directory, 현재 경로를 보여주기
  • which {프로그램}: 프로그램 위치 출력
    • ex which python3
  • ls : list, 현재 디렉토리 파일보기
    • ls -l : 상세 정보 포함하여 디렉토리 파일 보기(옵션을 넣을 수 있음)
  • cd .. : 현재 위치의 상위 경로로 이동하기
    • cd {절대경로}: 절대 경로로 이동하기
    • cd ~ : 기본 경로로 이동하기
  • rm {디렉토리명}: 비어있는 디렉토리 삭제하기
    • rm -rf {디렉토리명} : 디렉토리 강제 삭제하기(주의!)
  • cat {파일명} : 파일의 내용을 출력합니다.
  • grep {문자} {파일명}: 파일내에서 문자를 검색합니다.

✅ 리눅스 특징

  • 파란색 글자: 디렉토리(폴더)
  • 흰색 글자: 파일명
  • (.)으로 시작하는 디렉토리/파일 : 숨겨진 파일로서 ls -a로 확인 가능

회사 다니면서 알음알음 알아왔던 지식이여서 나름 다들 공통적으로 아는 내용이라고 생각했는데 생각보다 정리해보니 알려줄 내용이 꽤 많다. 암묵지의 형태는 생각보다 방대하고 정리할 가치가 있는 것 같다.  다음 글로는 GCP을 세팅하고 Crontab을 이용해 데이터 적재를 자동화하는  글을 작성할 예정이다. GoGo

 

+ Recent posts