git push시 계속해서 인증을 요구하는 경우
문제 상황 현재 프로젝트를 윈도우와 리눅스 컨테이너 양쪽에서 작업을 진행하던 중, github 토큰을 등록을 하게 되었다. 깃허브 홈페이지에서 성공적으로 토큰을 발급받고, 이를 활용하여 비밀번호를 입력한 후 성공적으로 push를 할 수 있게 되었다. 문제는 이후의 push 에서도 계속해서 비밀번호를 요구한다는 점이였다. 환경이 조금 특이해서 (도커 컨테이너 ubuntu, wsl) 다양한 방법을 시도해봤는데, 시간을 예상보다 많이 쏟게 되어 기록을 위해 글을 작성한다. Linux 환경 방법 0 git config --global credential.helper store 보안적으로 추천하지 않는 방법이다. git이 설치된 위치에서 git-core를 들어가면, git-credential-store라는 파일이..
2023.04.09
no image
[Kubeflow] pipeline
kubeflow란 💡 컨테이너 기반의 End-to-end ML 워크 플로우 관리 플랫폼 Kubeflow Pipeline의 구성 Experiment, Job, Run을 관리, 추적하고 실행할 수 있는 UI 파이프라인의 단계별 실행 스케쥴링을 위한 엔진 Python에서 파이프라인을 정의, 구축하기 위한 SDK SDK와 파이프라인의 실행을 위한 쥬피터 노트북 Pipeline의 지향점 쉬운 파이프라인 구성 쉬운 파이프라인 생성: Trial/ Experiement 컴포넌트들을 통해 다양한 기술 적용 가능 쉬운 재사용: 컨테이너 기반 구조 💡 간단하게 말해서 ML Pipeline이란, 워크플로우 컴포넌트를 통해 작업을 그래프 형태로 결합시킨 것! Pipeline의 실행 과정 파이프라인이 실행되면, 시스템은 각 단계..
2023.04.07
[Kubeflow] MLOps 개발환경 설정
구성 요소 제약 사항 kubernetes version >= 1.25 cpu >= 0.6 Storage >= 10GB Default Storage Class with dynamic provisioning 특히 default storage class를 생성하는 부분이 상당히 당황스러웠는데, minikube에서는 기본적으로 addon 형태로 달려있는 것을 확인할 있지만, kubeadm을 통해 만들어진 kubernetes 클러스터는 그렇지 않다. 직접 설정해줘야 한다. 이때 사용할 수 있는 대표적인 솔루션이 Rook과 Cephfs이다. CephFS is a distributed file system that provides scalable and reliable storage for files. It is a..
2023.04.07
스크립트를 통한 ssh 접속
ssh를 접속하는 가장 기본적인 방법은 bash의 ssh 명령어를 사용하는 것이다. 기본적인 사용방법은 다음과 같이 직관적이다. ssh 다만, 앞선 명령어는 현재 로컬 머신의 ip 주소를 그대로 가지고 서버에 접속을 시도하기 때문에, 해당 의도가 아닌 경우라면 비밀번호와 유저가 일치하지 않아 로그인이 안되는 문제가 있다. ssh @ 때문에 주로 위의 명령어처럼 유저를 명시하고 사용을 권장하곤 한다. 이는 argument로도 전달이 가능한데, 바로 -l 옵션을 통해서이다. ssh -l 여기까지가 기본적인 명령어 사용방법이였다. ssh 명령어가 있으니 이를 활용하여 ssh 접속을 스크립트 실행을 통해 간소화해보고 싶었다. 문제는 비밀번호를 넣는 부분인데, ssh 명령어를 통해서는 비밀번호를 스크립트로 넣을 ..
2023.04.07
SSH를 통한 Root 로그인 불가 시 해결 방법
접속하고자 하는 Server 터미널에서 sshd_config 파일을 열고 sudo nano /etc/ssh/sshd_config 해당 부분을 Yes로 바꿔준다. PermitRootLogin Yes 이후 sshd 재시작 해주며 변경 사항 적용해주면 끝. sudo systemctl restart sshd (써놨으니 안까먹겠지...)
2023.04.07
[프로그래머스] level 2 광물 캐기
구현 자체는 금방했는데, 1개 케이스에서 문제가 되서 시간을 조금 소모했습니다. 광물을 5개씩 잘라서, 가장 피로도를 많이 쓰는 경우부터 상위의 곡괭이를 사용했는데요. 단순하게 이렇게 계산해버리면, 테스트 케이스 8번에서 문제가 발생하는 것을 확인했습니다. 코드 상에서 표현되기를, 정렬을 통해 바뀌는 것은 광물의 순서이고 사실 곡괭이의 순서는 바뀌지 않습니다. 이렇게 되버리면, 광물을 캐는 순서는 정해져있지만, 마치 광물의 순서를 바꾸는 듯한 느낌을 받습니다. 일반적인 케이스에서는 광물을 기준으로 생각하든, 곡괭이를 기준으로 생각하든 사실 차이는 없으나, 문제는 곡괭이가 처리할 수 있는 광물의 개수가 전체 광물의 개수보다 적은 경우입니다. 때문에 이 경우에는 처리할 수 있는 광물만을 남겨 처리하도록 하는..
2023.03.28
logging 사용법과 관련 이슈
기본적인 사용방법 해당 글에서는 python의 logging 기본적인 사용방법을 다룬다. 관련해서 마주할 수 있는 에러 상황이나, 좀 더 다양한 상황을 다룰 수 있는 logging 사용방법을 기술해 보았다. 추가적으로 해당 글에서는 특정 메시지를 어떤 레벨로 로깅해야 하는지와 같은 철학은 다루지 않음을 감안해 주시면 좋겠다. 짧은 글이기에 바로 본론으로 들어가보자. 코드는 짧고 간결하다. import logging logging.basicConfig(filename='../logs/crawl.log', format='%(asctime)s %(levelname)s %(message)s') logging.getLogger().setLevel(logging.INFO) # Optional logging.info..
2023.03.27
Git 충돌날 때 사용가능한 간단한 대처방법
문제 상황 Local Repository (master) - A.cpp - B.cpp - AA.H Remote Repository (main) - README.md Remote Repository (master) - A.cpp - B.cpp - AA.H 상당히 이상한 예시지만, 실제로 위와 같은 상황에 대처해보는 방법을 전부터 작성해보고 싶어 이번 기회에 작성하고자 한다. 기존에는 그냥 간단하게 새로 clone 받아서 복구하는 형태로 충돌을 대처했다면, 만약 clone 외에 어떠한 방식으로 충돌을 해결할 수 있을 지를 남기고자 한다. 먼저, 문제 상황을 살펴보면 로컬/리모트 두 저장소가 서로 합쳐질 수 있는 듯 보이지만, master라는 어디선가 생겨버린 브랜치와 main 브랜치는 현재 아무런 접점이 없..
2023.03.26

문제 상황

현재 프로젝트를 윈도우와 리눅스 컨테이너 양쪽에서 작업을 진행하던 중, github 토큰을 등록을 하게 되었다. 깃허브 홈페이지에서 성공적으로 토큰을 발급받고, 이를 활용하여 비밀번호를 입력한 후 성공적으로 push를 할 수 있게 되었다. 문제는 이후의 push 에서도 계속해서 비밀번호를 요구한다는 점이였다. 환경이 조금 특이해서 (도커 컨테이너 ubuntu, wsl) 다양한 방법을 시도해봤는데, 시간을 예상보다 많이 쏟게 되어 기록을 위해 글을 작성한다.

Linux 환경

방법 0

git config --global credential.helper store


보안적으로 추천하지 않는 방법이다. git이 설치된 위치에서 git-core를 들어가면, git-credential-store라는 파일이 있는데, 거기에 토큰을 기록하는 방식이다. 사실 가장 편하긴 한데, 역시 보안적으로 취약하다는 점에서 권장되는 방식은 아니다.

방법 1

sudo apt-get install libsecret-1-0 libsecret-1-dev
cd /usr/share/doc/git/contrib/credential/libsecret
sudo make
git config --global credential.helper /usr/share/doc/git/contrib/credential/libsecret/git-credential-libsecret


원리는 libsecret이라는 패키지를 설치하여, 해당 패키지에 token을 저장하고 token이 필요한 경우 github는 명시한 credential.helper인 libsecret을 참고하여 인증을 수행한다.

여기까지해서 문제가 없으면 다행인데, 현재 docker 컨테이너 환경에서 작업을 진행하던 중 다음의 에러를 만날 수 있었다.

** (process:23898): CRITICAL **: 12:21:26.791: store failed: Cannot autolaunch D-Bus without X11 $DISPLAY


그래서 위의 방법이 안된다면 다른 방법을 시도해봐야 한다.

방법 2

그래서 어떻게 해결할 것이냐. git-credential-manger를 통해 토큰을 관리할 것이다.

가장 먼저 다음의 명령어로 수행이 가능하다면, 쉽게 갈 수 있다. git-credential-manager가 이미 설치된 경우이다.

git config --global credential.helper manager-core


근데, 내 컨테이너 환경에는 무슨 일이 일어났던 건지, 정상적인 git 경로에 credential-manager가 존재하지 않았다.

/usr/lib/git-core# ls git-credential*
git-credential  git-credential-cache  git-credential-cache--daemon  git-credential-store


그래서 직접 설치해주었다.

먼저 방법 1을 수행하지 않았다면 필요한 패키지를 설치해준다.

sudo apt-get install libsecret-1-dev


다음으로 credential-manager의 패키지 파일을 다운로드하고
(여기가 조금 오래 걸린다. 서버가 빠르지 않다... 최신 버전의 릴리즈는 더 느리다...)

wget https://github.com/microsoft/Git-Credential-Manager-Core/releases/download/v2.0.498/gcmcore-linux_amd64.2.0.498.54650.deb


패키지를 설치해준다.

sudo dpkg -i gcmcore-linux_amd64.2.0.498.54650.deb


여기까지 왔다면 설치는 완료되었고, 잘 설치되었나 확인 한번 해주자.

git credential-manager version


마지막으로 git에 설치한 credential-manager를 사용한다고 등록해주면 끝난다.

git config --global credential.helper manager-core


이후 push 수행 시 문제 없이 업로드가 가능해진다.

wsl, debian

방법 0

git config --global credential.helper store


보안적으로 취약하기에 권장되는 방식은 아니다. (위의 linux 부분을 참고)

방법 1

앞의 방법으로 credential.helper를 활용하면, 다음과 같은 에러 메세지를 만날 수 있다.

could not connect to Secret Service: Cannot autolaunch D-Bus without X11 $DISPLAY


위의 credential-manager를 설치해서 해결을 할 수도 있지만, 좀 더 쉬운 방법이 있다.

바로 윈도우 쪽에 설치된 git에서 자체적으로 제공하는 credential-manager의 경로를 직접 먹여주는 것이다.

git config --global credential.helper "/mnt/c/Program\ Files/Git/mingw64/libexec/git-core/git-credential-wincred.exe"


문제가 없다면 해당 명령 수행 이후 정상적으로 push가 추가적인 비밀번호 없이도 가능해진다.

만약 잘 안된다면 해당 경로에 git-credential-wincred.exe가 실제로 있는지 확인하고, 만약 없다면 직접 설치 이후, 설치된 위치의 credential-manager를 활용하는 방식으로 해결이 가능할 것 같다.

[Kubeflow] pipeline

mydailylogs
|2023. 4. 7. 16:58

kubeflow란

💡 컨테이너 기반의 End-to-end ML 워크 플로우 관리 플랫폼


Kubeflow Pipeline의 구성

  • Experiment, Job, Run을 관리, 추적하고 실행할 수 있는 UI

  • 파이프라인의 단계별 실행 스케쥴링을 위한 엔진

  • Python에서 파이프라인을 정의, 구축하기 위한 SDK

  • SDK와 파이프라인의 실행을 위한 쥬피터 노트북


Pipeline의 지향점

  • 쉬운 파이프라인 구성

  • 쉬운 파이프라인 생성: Trial/ Experiement 컴포넌트들을 통해 다양한 기술 적용 가능

  • 쉬운 재사용: 컨테이너 기반 구조

💡 간단하게 말해서 ML Pipeline이란, 워크플로우 컴포넌트를 통해 작업을 그래프 형태로 결합시킨 것!


Pipeline의 실행 과정

  1. 파이프라인이 실행되면, 시스템은 각 단계에 맞는 쿠버네티스 파드를 실행시킨다.

  2. 해당 파드는 설정된 컨테이너를 실행시킨다.

  3. 마찬가지로 해당 컨테이너는 내부의 애플리케이션을 실행한다.

  4. 이후 스케쥴러에 따라, 순서대로 컨테이너들을 실행시키는 형태로 파이프라인이 진행된다.


Pipeline 수행 과정 이해

 

Python SDK: 파이프라인 DSL을 통해 컴포넌트를 작성

In Python, DSL stands for Domain Specific Language. A DSL is a programming language that is specialized for a particular domain, such as data analysis, scientific computing, or web development. Some popular DSLs in Python include NumPy for numerical computing, Pandas for data analysis, and Django for web development.

 

  • DSL Compiler: 파이썬 코드를 YAML(쿠버네티스 리소스 양식)로 변환한다.

  • Pipeline Service: 쿠버네티스 리소스 양식에서 파이프라인을 생성하기 위해 호출

  • Kubernetes resources: Pipeline Service가 쿠버네티스 API 서버를 호출하여, 파이프라인 실행을 위한 쿠버네티스 리소스를 생성

  • Orchestration controllers: 생성된 쿠버네티스 리소스에 정의된 파이프라인을 실행하기 위한 할당 가능한 파드를 지정하며 컨테이너를 실행

  • Artifact storage: 실행된 파드에선 Meta-data → mysql 서버에 담고, 그 외의 큰 사이즈의 정보 → Minio 서버나, 별도 지정된 클라우드 스토리지에 저장

  • Pipeline web server: 실행된 파이프라인은 웹 UI를 통해 모니터링 가능


Component ( From 모두의 MLOPS)

💡 컴포넌트는 Component ContentsComponent Wrapper로 구성되며, 하나의 컴포넌트는 Wrapper를 통해 파이프라인에 전달되며 정의된 컴퍼넌트 콘텐츠를 실행(execute)하고 아티팩트(artifacts)를 생산합니다.

 

[출처] 모두의 mlops


Component Content

컴포넌트는 총 3가지로 구성됩니다.


1. Environment

2. Python code with Config

3. Generates Artifacts

예시와 함께 살펴보면

import dill
import pandas as pd

from sklearn.svm import SVC

train_data = pd.read_csv(train_data_path)
train_target= pd.read_csv(train_target_path)

clf= SVC(
    kernel=kernel
)
clf.fit(train_data)

with open(model_path, mode="wb") as file_writer:
     dill.dump(clf, file_writer)


다음과 같이 컴포넌트 콘텐츠로 나눌 수 있습니다.

[출처] 모두의 mlops


Environment는 파이썬 코드에서 사용하는 패키지들을 import하는 부분입니다.

다음으로 Python Code w\ Config 에서는 주어진 Config를 이용해 실제로 학습을 수행합니다.

마지막으로 아티팩트를 저장하는 과정이 있습니다.

Component Wrapper

컴포넌트 래퍼는 컴포넌트 콘텐츠에 필요한 Config를 전달하고 실행시키는 작업을 합니다.

[출처] 모두의 mlops


Kubeflow에서는 컴포넌트 래퍼를 위의 train_svc_from_csv와 같이 함수의 형태로 정의합니다. 컴포넌트 래퍼가 콘텐츠를 감싸면 다음과 같이 됩니다.

[출처] 모두의 mlops

Artifacts

위의 설명에서 컴포넌트는 아티팩트(Artifacts)를 생성한다고 했습니다. 아티팩트란 evaluation result, log 등 어떤 형태로든 파일로 생성되는 것을 통틀어서 칭하는 용어입니다. 그 중 다음과 같은 값을 주로 작업하게 됩니다.

[출처] 모두의 mlops

  • Model: ML 모델
  • Data: 데이터는 전 처리된 피처, 모델의 예측 값 등을 포함
  • Metric
    • 동적 지표: 학습과 함께 변화하는 값 (Train loss)
    • 정적 지표: 최종적으로 모델을 평가하는 지표 (Accuracy)
  • etc
💡 "모델이란 파이썬 코드와 학습된 Weights와 Network 구조 그리고 이를 실행시키기 위한 환경이 모두 포함된 형태"
- 모두의 MLOps


다시 파이프라인으로 돌아오자면 …

[출처] 모두의 mlops

파이프라인은 컴포넌트의 집합과 컴포넌트를 실행시키는 순서도로 구성되어 있습니다. 이때 순서도는 방향이 없는 그래프(Undirected Graph)로 표현되며, 간단한 조건문을 포함할 수 있습니다.

 

Pipeline Config

[출처] 모두의 mlops

컴포넌트를 실행시키기 위해 Config가 필요합니다. 이때 파이프라인을 구성하는 컴포넌트의 Config들을 모아둔 것이 파이프라인 Config입니다.

Run

  • Config 까지 주어진다면 → 파이프라인 실행가능

  • 이때 실행된 파이프라인 명세를 Run이라고 부름 (명세의 인스턴스화)


Run은 파이프라인의 실행 단위로서 앞서 그래프 형태로 표현된 파이프라인의 명세를 순서대로 실행합니다.

파이프라인이 실행되면 각 컴포넌트가 아티팩트들을 생성합니다. Kubeflow pipeline에서는 Run 하나당 고유한 ID 를 생성하고, Run에서 생성되는 모든 아티팩트들을 저장합니다.


Recurring Run

  • 주기적으로 파이프라인을 실행하는 Run
  • 대표적으로 Cron 형태로 수행가능 → 배치성 작업이나 모니터링 작업에 적합
  • Run Trigger을 통해 관리


Run Trigger

Run의 실행여부를 결정하는데 활용 (일종의 Flag)

  • Periodic: 간격 기반의 스케쥴링 (Ex. 30분에 한번 실행)
  • Cron: cron 형태의 스케쥴링 (Cron 형태란)


Step

  • 파이프라인 상에서 하나의 컴포넌트의 실행
  • 중첩/분기되어 실행되기도 함


Experiment

  • 파이프라인을 실행하는 워크스페이스
  • 파이프라인 실행을 논리적으로 묶어주는 역할 (K8s의 NS와 유사)
  • 별도 지정하지 않는 한 default라는 Experiment (group)에서 Run이 실행


Pipeline SDK

💡 파이프라인 SDK는 파이썬 패키지로 구성되어 있으며, 크게 5가지의 영역으로 나뉘어져 있습니다.


kfp.compiler

  • 파이프라인 컴포넌트를 빌드하는 클래스와 메소들의 패키지


kfp.components

  • 파이프라인 컴포넌트들을 다루는 클래스와 메소드들의 패키지

kfp.dsl

  • domain-specific language

  • 파이썬 코드를 통해 파이프라인을 작성하기 위해 필요한 클래스와 메소드, 모듈들을 모아둔 패키지


kfp.Client


파이프라인 API와 통신하는 파이썬 클라이언트
라이브러리 패키지


kfp.containers


컨테이너 이미지관련 메소드와 클래스들의 패키지


SDK로 파이프라인 만들기


파이프라인은 DSL을 통해서 작성되며, 컴파일 과정을 거쳐 쿠버네티스 파이프라인 리소스로 변환되어 사용됩니다.


파이프라인은 2가지 방법으로 작성이 가능합니다.


ksp.dsl 내부의 ContainerOp
를 이용하여 생성


이때, 각 Step에서 필요한 테스크를 수행하는 애플리케이션이 미리 작성이 되어 있어야 하며, 해당 애플리케이션은 도커 이미지로 패키징되어 단독으로도 실행이 가능해야 합니다.


이때, 도커 이미지는 ContainerOp의 매개변수를 통해 지정됩니다.

classkfp.dsl.BaseOp(
        name: # 컴포넌트 이름
        image: # 컨테이너 이미지 이름
        Command: # StringOrStringList 컨테이너 실행 명령어
        arguments: # StringOrStringList 컨테이너 실행 인자값
        ...
)


이를 활용한 파이프라인 정의

from kfp import ds1

def echo_op():
    return ds1.ContainerOp(
            name='echo',
            image='library/bash:4.4.23',
            command=['sh', '-c'],
            arguments=['echo "Hello World"'],
    )

@dsl.pipeline(
    name='ContainerOp pipeine',
    description="..."
)
def hello_world_pipeline():
    echo_task = echo_op()


활용 예시 1 - 쥬피터 노트북

if __name__ == "__main__":
    kfp.compiler.Compiler().complie(hello_world_pipeline, 'containerop.pipeline.tar.gz')



활용 예시 2 - dsl-compile

$ dsl-compile --py container.py --output containerpop.pipeline.tar.gz


파이썬 함수를 통해 생성


생성 예시

import kfp.dsl as dsl

@dsl.pipeline (
    name='example_1',
    description='A simple pipeline'
)
def my_pipeline(a: int = 1 b: str = "default value"):
    print(a)
    print(b)


활용 예제

if __name__ == "__main__":
    import kfp.compiler as compiler
    compiler.Compiler().compile(my_pipeline, 'my_pipeline.pipeline.tar.gz')
   
구성 요소 제약 사항
kubernetes version >= 1.25
cpu >= 0.6
Storage >= 10GB
Default Storage Class with dynamic provisioning

 

특히 default storage class를 생성하는 부분이 상당히 당황스러웠는데, minikube에서는 기본적으로 addon 형태로 달려있는 것을 확인할 있지만, kubeadm을 통해 만들어진 kubernetes 클러스터는 그렇지 않다. 직접 설정해줘야 한다.

이때 사용할 수 있는 대표적인 솔루션이 Rook과 Cephfs이다.

CephFS is a distributed file system that provides scalable and reliable storage for files. It is a component of the larger Ceph storage system and can be used as a standalone file system or as part of a Ceph cluster. CephFS is designed to provide POSIX-compliant file system semantics and is highly available and fault-tolerant.

 

Rook has built-in support for Ceph, which means that it can be used to deploy and manage a Ceph cluster within a Kubernetes environment. This includes managing CephFS as a storage solution within the Kubernetes cluster. Rook abstracts the complexities of managing Ceph and exposes a simplified API for deploying and managing the storage system.

 

Rook integrates with CSI to provide a standard interface for integrating different storage systems with Kubernetes. Rook uses CSI drivers to manage different storage systems, such as Ceph or CockroachDB, within a Kubernetes environment. The CSI drivers provide a standard set of APIs that Rook can use to manage the storage systems, making it easier for users to deploy and manage different storage solutions within their Kubernetes environment.


요약하자면, ceph는 분산 파일 스토리지, Rook은 이를 오케스트레이션해주는 서비스이다. 이때, 쿠버네티스 내의 각기 다른 파일시스템(Ceph/CockroachDB 등)에 대해서 Rook은 CSI(Container Storage Interface)에서 제공하는 Interface를 통해 비교적 쉽게 제어가 가능하다는 장점이 있다고 한다.

 

Step 1 Install LVM

Ceph는 raw block device, partitions, LVM logical volumnes를 포함하여 다양한 종류의 block storage device를 사용할 수 있는데, 해당 문서에서는 이슈 발생을 최대한 피하기 위해 raw device에 LVM을 설치하는 방식으로 진행한다.

 

먼저 해당 머신에 ceph 구성에 활용될 수 있는 partiion이나 device가 있는지를 확인한다.

lsblk
lsblk -f

 


이떄 만약 FSTYPE 필드가 비어있지 않다면, 해당 장치 위에 파일 시스템이 있다는 것을 의미한다. 해당 문서에서는 _sdb_를 사용하여 Ceph를 구성하기로 한다.

 

sudo apt-get install -y lvm2

 

Step 2 - Install Rook

 

현재 가장 최신 버전인 1.11.1 버전을 설치

 

git clone --single-branch --branch v1.11.1 https://github.com/rook/rook.git

 

cd rook/deploy/examples
kubectl create -f crds.yaml -f common.yaml -f operator.yaml


CRD, ClusterRole과 RoleBinding이 생성되는 것을 볼 수 있다.

 

이후 Rook 클러스터를 생성해준다.

 

kubectl create -f cluster.yaml

 


성공이 되었는지 확인해주자.

 

kubectl get CephCluster -n rook-ceph


Step 3 - Provision Storage Class and make it default

 

ceph를 도입한 당초 목적이였던 Storage Class를 프로비저닝하고 이를 쿠버네티스의 default 스토리지로 설정하는 단계이다.

 

마찬가지로 앞서 Clone 해왔던 Rook의 저장소에서 다음의 주소에 있는 yaml 파일을 적용시켜 준다.

(Rook/deploy/examples/csi/rbd)

 

kubectl apply -f storageclass-test.yaml


storage 클래스가 생성되었다면, 이를 default로 설정해보자

kubectl patch sc rook-ceph-block -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

 

Step 4 - Install kustomize

그동안 3.2.0 버전의 kustomize를 고수해오던 kubeflow가 2023년 2월경부터 5.0.0 버전을 요구하기 시작했다. (sortOptions라는 새로운 필드를 사용하기 위해서라고 한다)

kustomize repo에 들어가서 머신에 적합한 압축파일을 받아준다.

kustomize 릴리즈

 

Releases · kubernetes-sigs/kustomize

Customization of kubernetes YAML configurations. Contribute to kubernetes-sigs/kustomize development by creating an account on GitHub.

github.com

 

wget https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv5.0.1/kustomize_v5.0.1_linux_amd64.tar.gz


압축 풀어주고

tar -xvf kustomize_v5.0.1_linux_amd64.tar.gz


PATH 경로로 옮겨주면 구성 완료

 

mv kustomize /usr/local/bin


버전 확인해주며 마무리

 

kustomize version

Step 5 - Install Kubeflow

 

쿠버네티스를 설치하는 방식에는 크게 두 가지가 있다.

 

1. Single-command installation of all components under apps and common

2. Multi-command, individual components installation for apps and common

2번의 경우 각각의 개별적인 구성 요소들을 독립적으로 설치하는 옵션이다. 해당 사이트에 들어간 이후 설명대로 복사 붙여넣기만 하면 된다. 다만, 상당히 귀찮은 것을 확인할 수 있다.

 

해당 문서에서 진행하고자 하는 것은 Single-command로 명령어 한줄로 manifest들을 적용하여 kubeflow의 모든 구성요소를 설치하는 옵션이다.

 

manifest를 일단 가져와서 준비해준다.

 

cd ~
git clone https://github.com/kubeflow/manifests.git
cd manifests

 


다음 명령어를 통해 설치를 진행한다.

 

while ! kustomize build example | awk '!/well-defined/' | kubectl apply -f -; do echo "Retrying to apply resources"; sleep 10; done

 


약 10분 가량의 시간이 흐르고 설치가 완료되었다.

 

Step 6 - Connect to your Kubeflow Cluster (Port-Forward)

 

설치가 완료되었다면, Pod이 완전히 다 뜨는 것을 확인 후에 다음 단계로 넘어가자

 

kubectl get pods -n cert-manager
kubectl get pods -n istio-system
kubectl get pods -n auth
kubectl get pods -n knative-eventing
kubectl get pods -n knative-serving
kubectl get pods -n kubeflow
kubectl get pods -n kubeflow-user-example-com

 

kubeflow에 접근하는 기본적인 방식은 Port-forwarding이며, 이를 통해 해당 kubernets 환경에 별도 요구사항 없이도 kubeflow와 유저 간의 연결이 가능해진다.

 

앞서 설치된 Istio의 ingressgateway를 port-forwarding하여 Local Port 8080을 Remote port 80(pod 쪽)로 연결한다.

 

이후 다음을 통해 Kubeflow Central Dashboard에 접속한다. 1. 브라우저를 열고 http://localhost:8080으로 접속한다. Dex login 화면이 보인다면 성공 2. default user credential로 로그인한다. default email 주소는 user@example.com이고 비밀번호는 12341234이다.

NodePort/LoadBalancer/Ingress를 사용하기 위해서는 HTTPS를 사용해야만 한다. 이는 kubeflow의 많은 web app들이 Secure Cookie를 사용하고 있기 때문이다.

ssh를 접속하는 가장 기본적인 방법은 bash의 ssh 명령어를 사용하는 것이다.

기본적인 사용방법은 다음과 같이 직관적이다.

ssh <ip-address>

다만, 앞선 명령어는 현재 로컬 머신의 ip 주소를 그대로 가지고 서버에 접속을 시도하기 때문에, 해당 의도가 아닌 경우라면 비밀번호와 유저가 일치하지 않아 로그인이 안되는 문제가 있다.

ssh <username>@<ip-address>

때문에 주로 위의 명령어처럼 유저를 명시하고 사용을 권장하곤 한다.

이는 argument로도 전달이 가능한데, 바로 -l 옵션을 통해서이다.

ssh -l <username> <ip-address>

 

여기까지가 기본적인 명령어 사용방법이였다.

ssh 명령어가 있으니 이를 활용하여 ssh 접속을 스크립트 실행을 통해 간소화해보고 싶었다.

문제는 비밀번호를 넣는 부분인데, ssh 명령어를 통해서는 비밀번호를 스크립트로 넣을 수가 없었다.

-p 옵션이 있기 때문에, 될 것 같은 느낌도 들지만 해당 옵션은 포트를 명시하는 옵션이다.

때문에, 비밀번호를 가지고 ssh 접속을 하기 위해서는 sshpass 라는 패키지를 설치해줘야 한다.

# Ubuntu or Debian
sudo apt-get install sshpass

# macOS
brew install http://git.io/sshpass.rb

이후 다음 명령어를 통해 비밀번호를 스크립트 안에 밀어넣을 수 있다.

sshpass -p <password> ssh -l <username> <ip-address>

 

ssh가 보안 상의 이유로 비밀번호를 노출시키지 못하도록 한 것을 sshpass가 끄집어냈다는 느낌이 든다. 사실 근데 대부분의 상황에서 편리하자고 sshpass를 쓰는게 맞을까 하는 의문은 든다. 혹여나 스크립트가 노출이 되어버리면 곧바로 컴퓨터에 대한 모든 권한을 넘겨주는 것이기에, sshpass는 최대한 지양하는 것이 맞을 것 같다.

그럼에도 ssh를 자동으로 접속해야만 하는 불가피한 상황이 있을 수 있으니 해당 상황에서는 꼭 방화벽을 철처하게 설정해줘야 하지 싶다.

접속하고자 하는 Server 터미널에서

sshd_config 파일을 열고

sudo nano /etc/ssh/sshd_config

 

해당 부분을 Yes로 바꿔준다.

PermitRootLogin Yes

 

이후 sshd 재시작 해주며 변경 사항 적용해주면 끝.

sudo systemctl restart sshd

 

(써놨으니 안까먹겠지...)

구현 자체는 금방했는데, 1개 케이스에서 문제가 되서 시간을 조금 소모했습니다.

광물을 5개씩 잘라서, 가장 피로도를 많이 쓰는 경우부터 상위의 곡괭이를 사용했는데요.

단순하게 이렇게 계산해버리면, 테스트 케이스 8번에서 문제가 발생하는 것을 확인했습니다.

코드 상에서 표현되기를, 정렬을 통해 바뀌는 것은 광물의 순서이고 사실 곡괭이의 순서는 바뀌지 않습니다.

이렇게 되버리면, 광물을 캐는 순서는 정해져있지만, 마치 광물의 순서를 바꾸는 듯한 느낌을 받습니다.

일반적인 케이스에서는 광물을 기준으로 생각하든, 곡괭이를 기준으로 생각하든 사실 차이는 없으나, 문제는 곡괭이가 처리할 수 있는 광물의 개수가 전체 광물의 개수보다 적은 경우입니다.

때문에 이 경우에는 처리할 수 있는 광물만을 남겨 처리하도록 하는 별도의 코드가 있어야 합니다.

def diamond_and_iron_counts(seq):
    return seq[0], seq[1]

def diamond_and_iron_key(seq):
    return diamond_and_iron_counts(seq)

def solution(picks, minerals):
    answer = 0
    upper_bound = 0
    sequences = []
    
    if sum(picks) * 5 < len(minerals):
        minerals = minerals[0:sum(picks)*5]
        
    while upper_bound < len(minerals):
        upper_bound = upper_bound + 5 if upper_bound < len(minerals) else len(minerals)
        sublist = minerals[upper_bound - 5: upper_bound]
        dia = sublist.count("diamond")
        iron = sublist.count("iron")
        stone = sublist.count("stone")
        sequences.append([dia, iron, stone])
    
    sorted_sequences = sorted(sequences, key=diamond_and_iron_key)
    equipments = [[1, 1, 1], [5, 1, 1], [25, 5, 1]]
    for i in range(picks[0]):
        if sorted_sequences:
            answer += sum([x * y for x, y in zip(equipments[0], sorted_sequences.pop())])
        
    for i in range(picks[1]):
        if sorted_sequences:
            answer += sum([x * y for x, y in zip(equipments[1], sorted_sequences.pop())])
        
    for i in range(picks[2]):
        if sorted_sequences:
            answer += sum([x * y for x, y in zip(equipments[2], sorted_sequences.pop())])

    return answer

기본적인 사용방법

해당 글에서는 python의 logging 기본적인 사용방법을 다룬다. 관련해서 마주할 수 있는 에러 상황이나, 좀 더 다양한 상황을 다룰 수 있는 logging 사용방법을 기술해 보았다.

 

 

추가적으로 해당 글에서는 특정 메시지를 어떤 레벨로 로깅해야 하는지와 같은 철학은 다루지 않음을 감안해 주시면 좋겠다.

 

 

짧은 글이기에 바로 본론으로 들어가보자. 코드는 짧고 간결하다.

 

 

import logging

logging.basicConfig(filename='../logs/crawl.log', format='%(asctime)s %(levelname)s %(message)s')
logging.getLogger().setLevel(logging.INFO) # Optional

logging.info("This message is logged with INFO level")

logging.warn("This message is logged with WARNING level")

 

 

이 정도의 코드만으로도 대부분의 파이썬 프로그램은 로깅이 가능하다.

 

 

logging.basicConfig()

 

 

로깅을 기록하기 위한 기본적인 설정이 이뤄진다. 위의 코드에서는 파일 이름과 로깅 시 메시지에 앞서 기록될 템플릿이 명시되어 있다.

 

 

주의해야 할 점은 명시한 파일의 경우, logging이 자동으로 생성해주지 않는다는 것이다. 직접 touch나 vi를 통해 생성해야 한다.

 

 

logging.getLogger().setLevel(logging.INFO)

logging의 기본 로깅 레벨은 WARNING으로 INFO의 레벨로 설정된 메시지는 로그에 남지 않는다. 해당 코드에서는 INFO까지도 기록하기 위해 해당 라인을 추가해 보았다.

 

 

logging.info("This message is logged with INFO level")

INFO 레벨의 로깅이다.

 

 

logging.warn("This message is logged with WARN level")

WARN 레벨의 로깅이다.

 

 

다만 이렇게 했는데도, 해당 파일에 로깅이 되지 않는 문제가 생길 수 있다.

경험상 순서대로 3가지 정도를 점검해 볼 수 있다

1. 경로가 정확한지, 또 파일은 정상적으로 생성이 된 상태인지

2. 로깅 레벨이 당초 의도한 대로 설정되어 있는지 (WARN인 경우 INFO는 당연히 기록되지 않는다.)

3. 권한이 충분한지, py 파일이 로그 파일에 접근하여 쓰기 작업을 진행할 수 있는 권한이 있는지

 

 

이 정도 문법만으로도 사실 충분하지만 handler라는 친구를 통해 더욱 유연하게 상황에 맞는 로그를 작성할 수 있다.

 

logging.FileHandler

import logging

# logging 객체를 생성한다
logger = logging.getLogger('my_logger')
logger.setLevel(logging.DEBUG)

# 디버그 레벨 핸들러를 생성한다
debug_handler = logging.FileHandler('../logging/debug.log')
debug_handler.setLevel(logging.DEBUG)
debug_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
debug_handler.setFormatter(debug_formatter)

# 에러 레벨 핸들러를 생성한다
error_handler = logging.FileHandler('error.log')
error_handler.setLevel(logging.ERROR)
error_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
error_handler.setFormatter(error_formatter)

# 생성한 logging 객체에 핸들러들을 등록한다
logger.addHandler(debug_handler)
logger.addHandler(error_handler)

# 로깅
logger.debug('This is a debug message')
logger.info('This is an info message')
logger.warning('This is a warning message')
logger.error('This is an error message')

 


Handler의 역할은 다양할 수 있지만, 앞선 코드와 같이 다양한 핸들러가 각기 다른 로깅 레벨에 맞춰 해당 메시지를 처리하는 경우에 활용할 수 있다.

 

 

debug_handler = logging.FileHandler('../logging/debug.log')

 

 

파일 핸들러를 만든다. 이때의 파라미터는 작성될 파일의 이름이다. 앞선 basicConfig와 동일하게 파일은 생성해주지 않는다.

 

 

debug_handler.setLevel(logging.DEBUG)
debug_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') 
debug_handler.setFormatter(debug_formatter)

 

 

예상하셨겠지만, 디폴트 레벨 설정 부분과 로그 메시지의 앞쪽에 붙게 되는 템플릿이다.

 

 

이후 생성한 핸들러를 객체에 등록하면 설정이 완료된다.

 

 

logger.addHandler(debug_handler)

 

 

아래의 getLogger 메소드는 로깅 객체를 반환한다. 

 

 

logger = logging.getLogger('my_logger')

 

 

 

my_logger는 해당 logging object를 식별할 수 있는 일종의 키이다. 이후에 다시 my_logger를 getLogger에 넣어준다면 동일한 로깅 객체가 반환된다.

 

 

 

사실 FileHandler 말고 기능이 좀 더 풍부한 RotatingFileHandler와 TimeRoatatingFileHandler라는 클래스도 존재한다. 대단한 친구들은 아니고, FileHandler보다 좀 더 풍부한 기능들을 제공한다고 생각하면 된다. FileHandler를 상속받는 친구들이다.


FileHandler의 경우 다소 한계점들이 존재한다. 만약 로그 파일이 특정 사이즈에 도달하 경우 FileHandler는 기존 파일에 작성하던 걸 멈추고 파일을 닫는다. 결과적으로 로깅이 멈춘다.


반면 RotatingFileHandler, TimeRoatatingFileHandler에서는 FileHandler와 거의 동일한 역할을 수행하지만 몇 가지 추가적인 기능이 붙어있다.


예를 들어 RotatingFileHandler의 경우, maxBytes를 통해 최대 몇 바이트까지 쓰게 되면, 기존 파일을 닫고 새로운 파일을 열어 로깅을 이어나가도록 지정할 수 있다. (기본값은 0으로 닫지 않고 쭉 작성)


backupCount라는 옵션을 통해 백업의 최대 개수를 지정할 수도 있다. (기본값은 0)

'Programming Language > Python' 카테고리의 다른 글

게으른 로깅  (0) 2023.07.29
[pytest] @pytest.fixture  (0) 2023.05.02

문제 상황

Local Repository (master)
- A.cpp
- B.cpp
- AA.H

Remote Repository (main)
- README.md

Remote Repository (master)
- A.cpp
- B.cpp
- AA.H

상당히 이상한 예시지만, 실제로 위와 같은 상황에 대처해보는 방법을 전부터 작성해보고 싶어 이번 기회에 작성하고자 한다.
기존에는 그냥 간단하게 새로 clone 받아서 복구하는 형태로 충돌을 대처했다면, 만약 clone 외에 어떠한 방식으로 충돌을 해결할 수 있을 지를 남기고자 한다.

먼저, 문제 상황을 살펴보면 로컬/리모트 두 저장소가 서로 합쳐질 수 있는 듯 보이지만, master라는 어디선가 생겨버린 브랜치와 main 브랜치는 현재 아무런 접점이 없어, 히스토리를 서로 추적할 수 없는 상황이다.

그래서 우선적으로 local에 main 브랜치를 생성해주고 이동해주었다.

git branch main
git checkout main

이후 동일하게 add, commit, push 를 진행해주었다.

git add .
git commit -m "my commit message"

그럼에도 여전히 push 명령어는 오류를 발생시켰다.

git push origin main
To https://github.com/mukmookk/repo.git
 ! [rejected]        main -> main (fetch first)
error: failed to push some refs to 'https://github.com/mukmookk/repo.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

여전히 현재 로컬의 main 브랜치와 리모트 main 브랜치 간의 충돌이 발생함을 확인할 수 있다.

이는 로컬의 파일들과 리모트에 위치한 README.md 간의 히스토리가 일치하지 않기 때문일 것이다.

때문에 명령어의 힌트에 나온 것처럼 pull 명령어를 시도해보았다.

git pull origin main
From https://github.com/mukmookk/repo
 * branch            main       -> FETCH_HEAD
fatal: refusing to merge unrelated histories

그러나 별도의 옵션이 존재하지 않는 pull 명령어는 여전히 오류를 발생시켰고, 이는 push가 실패한 이유와 크게 다르지 않은 것으로 추정된다.

해결

다만, 히스토리가 다르더라도 병합될 브랜치 파일들 간에 간섭이 없는 위의 상황이라면 pull의 --allow-unrelated-histories 옵션이 해결책일 수 있다.

git pull origin main --allow-unrelated-histories
From https://github.com/mukmookk/repo
 * branch            main       -> FETCH_HEAD
Merge made by the 'ort' strategy.
 README.md | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 README.md

위의 명령어는 서로 관련없는 히스토리를 가졌다고 해도, 두 브랜치 간의 머지를 허용해주는 옵션이다. 때문에 앞서 제시된 문제상황과 같은 경우에 해당 명령어를 작성해주면 다음과 같이 로컬 저장소에 머지가 된다.

Local Repository (master)
- A.cpp
- B.cpp
- AA.H
- README.md

이를 다시 push 해주면 당초 목표했던 push가 가능해진다.

git push origin main
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 4 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 366 bytes | 366.00 KiB/s, done.
Total 2 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/mukmookk/repovvvvvvvv.git
   7923c39..545efbd  main -> main