BE

Backend #


2025-08-13 ⋯ python #2 객체지향 프로그래밍, 병렬처리

1. 객체지향 프로그래밍 property & dataclass (p.139-140) @property - diameter 메서드는 사실 _radius * 2라는 계산을 수행하지만 외부에선 c.diameter라고 쓰면 바로 10이라는 결과를 얻을 수 있다. - @diameter.setter를 사용하면 c.diameter = 20 형태로 diameter을 수정할수있고 - 내부에서는 diameter을 받아 _radius=10으로 변환 저장한다. - fastapi에서 젤많이쓰는 기능이 속성화이다. @dataclass - 보통 클래스를 만들면 __init__으로 생성자, __repr__으로 객체 출력 형식, __eq__로 동등성 비교 등을 직접 정의해야 하는데 @dataclass를 붙이면 이런 메서드들이 자동 생성된다. - Point 클래스는 x, y 좌표만 Point(1,2)로 정의했는데 이 상태로 객체 p1, p2를 생성하고 출력하면 Point(x=1, y=2)처럼 형식맞춰 나온다. - 그리고 == 비교 시 자동으로 True도 나온다. @property - class Order - 주문 정보를 저장하는 클래스 - 속성: beverage, quantity - __slots__를 사용해 이 두 속성만 인스턴스에 저장할 수 있도록 제한했기 때문에 메모리 사용량이 줄고 실수로 다른 속성을 추가하는 것도 방지함. - total_price 메서드 - @property로 정의됨 - 주문 금액을 계산하는 로직을 담고 있지만 속성 접근처럼 쓸 수 있다 즉 order.total_price()가 아니라 order.total_price로 쓸수있다. cf) @property 안썼으면? - @property 사용하면 order.total_price로 괄호 없이 접근했을때, 내부에서 계산된 결과가 바로 반환되고 - @property 사용 안하면 order.total_price()로 호출하면 6000이 나오고 괄호없이 호출하면 메서드 객체 참조만 나온다. @dataclass - class Beverage - 음료 정보를 저장하는 데이터 전용 클래스 - 속성: name(문자열), price(실수형), tags(문자열 리스트) - @dataclass로 자동으로 `__init__`(생성자), `__repr__`(객체를 보기 좋게 출력), `__eq__`(값 비교) 같은 기본 메서드가 생성. cf) @dataclass 안썼으면? - `__init__` : 매개변수를 받아 속성을 초기화 / `__repr__` : 객체를 보기 좋게 문자열로 표현 / `__eq__` : 객체 간 동등성 비교 로직 작성 이렇게 하나하나 추가해야한다. decorator & closer (p.168-169) decorator - 데코레이터 (timer) - 함수 실행시간을 자동으로 측정 - 내부에 wrapper 함수를 정의해서 slow function을 감싼다. - 흐름 - wrapper는 시작시간기록, slow function 실행결과를 result에 저장하고 종료시간 기록, 걸린시간 계산, result를 반환 - @timer -> slow function을 호출하면 사실상 wrapper가 실행된다. wrapper 안에서 slow function이 호출 -> 2초 대기 -> 작업완료 출력 -> 실행시간 result 출력 - 의의 - 함수를 호출하기 전후에 원하는 로직을 끼워 넣어 원래 함수의 기능은 그대로 두고 부가적인 기능을 쉽게 추가할 수 있게. closure - outer()가 실행되면? - x = 10이 만들어지고 inner 함수가 정의됨 - outer()는 inner 함수를 그 자체로 반환함 (inner의 결과를 반환하는게 아니고) - closure = outer()? - closure에 **inner 함수**가 저장 - 이때 inner 함수는 자신이 정의될 당시의 환경(= x=10이 있던 outer의 스코프)을 함께 기억함 - 그래서 outer가 끝나서 x 변수가 사라진 것처럼 보여도 closure()를 실행하면 여전히 x = 10에 접근 가능. - closure는 decorator처럼 @문법을 붙이지 않아도 적용된다. decorator와 closure 함께사용하기 decorator - 데코레이터 measure_time - 실행 시간 측정 - 내부에 wrapper 함수를 정의해서 run_typed을 감싼다. - 흐름 - wrapper는 run_typed 실행결과를 elapsed_time에 저장한 뒤 반환 - @measure_time -> run_typed을 호출하면 사실상 wrapper가 실행된다. wrapper 안에서 run_typed가 호출 -> 실행시간 elapsed_time 출력 closure - measure_time이 실행되면? - wrapper 함수가 정의됨, measure_time은 wrapper 함수를 그 자체로 반환함 (wrapper의 결과를 반환하는게 아니고) - run_typed에 @measure_time이 적용되면? - run_typed 함수 객체가 measure_time의 매개변수 func로 전달 - measure_time 안에서 정의된 wrapper 함수는 자신이 정의될 당시의 환경(자기 바깥 함수의 지역 변수인 func)를 기억 - 그래서 measure_time이 종료되어 원래 지역 변수 func가사라진 것처럼 보여도 wrapper 함수 내부에는 여전히 func에 대한 참조가 살아 있다. 2. 병렬처리 multithreading (p.189) - 스레드가 같은 프로세스 내부에서 실행되며 메모리와 실행 환경을 공유 - 예제 코드 - print_numbers와 print_letters를 각각 thread1 thread2로 실행 - 결과 - 숫자 1부터 5까지와 알파벳 A부터 E까지가 1초 간격으로 번갈아 출력 mutliprocessing (p.191) - 함수가 완전히 독립된 프로세스로 실행 - 예제 코드 - print_numbers와 print_letters를 독립적인 프로세스 process1 process2로 실행 - 결과 - 두 프로세스가 동시에 시작되더라도 실행 타이밍과 OS 스케줄링 우선순위, 프로세스 생성 시점의 지연 때문에 한 프로세스가 먼저 실행을 많이 진행하고 다른 프로세스가 뒤따라 실행되게되고 - 그 결과 숫자 1-5를 전부 찍고 난 후 알파벳 A-E를 찍는 식으로 출력이 묶음 단위로 나타난다. multithreading & mutliprocessing multithreading - 두 스레드가 같은 프로세스 내부에서 실행되며 메모리와 실행 환경을 공유한다. - 예제에서 숫자를 찍는 함수와 알파벳을 찍는 함수 각각이 독립적인 스레드로 동작하지만 동일한 프로세스의 GIL(Global Interpreter Lock)을 공유하기 때문에 한 번에 한 스레드만 실제로 파이썬 바이트코드를 실행한다. - time.sleep(1)로 실행 권한을 번갈아 준 결과 숫자를 하나 찍고 잠시 멈춘 사이 다른 스레드가 알파벳을 찍는 식으로 출력이 교차되고 실행 타이밍에 따라 순서가 조금씩 섞여 나타난다 즉 두 작업이 거의 동시에 진행되는 것처럼 보이지만 사실은 GIL과 sleep 호출에 의해 미세하게 번갈아 실행된다. mutliprocessing - 각 함수가 완전히 독립된 프로세스로 실행된다. - 두 프로세스가 동시에 시작되더라도 실행 타이밍과 OS 스케줄링 우선순위 때문에 한 프로세스가 먼저 실행을 많이 진행하고 다른 프로세스가 뒤따라 실행되게되고 그 결과 숫자 1-5를 전부 찍고 난 후에 알파벳 A-E를 찍는 식으로, 출력이 묶음 단위로 나타나게 된다. ~*사실잘모르겟다...어렵다,,,,*~ 결론 - 멀티스레딩은 하나의 프로세스 안에서 협력적으로 실행을 나누기 때문에 출력이 교차되거나 순서가 섞이기 쉽고, 멀티프로세싱은 프로세스 단위로 완전히 병렬 실행되지만 OS 스케줄링 특성상 한쪽이 먼저 실행을 마쳐 출력이 블록처럼 모이는 경우가 많다. MutClust에서 mutliprocessing 코드 MutClust 예전 utils 코드중에서 병렬처리 코드 있었던거같아서 찾아봣다 흐름은 1. multiprocessing.Pool을 이용해 최대 50개의 프로세스를 동시에 실행할 수 있도록 풀을 생성 2. target_dir 디렉토리 내 파일이 meta_df의 인덱스 이름에 포함되어 있는 경우만 남겨서 mutInfo_files 생성 3. mutInfo_files를 pool.map(process_mutInfo, mutInfo_files)에 전달 - process_mutInfo: 병렬로 process_mutInfo 함수에 의해 처리(mutInfo_files를 읽고 sid, mutInfo_df 생성) 4. key가 sid, 값이 mutInfo_df인 딕셔너리 seq_dict로 만들고 total_df로 정리 결론 - 변이 정보를 병렬 프로세스(50개)로 빠르게 처리하고 결과를 df로 정리해서 저장해놓고 썼다. - 결과파일 저장해놓은뒤로 사용한적없어서 utils에서 빠진거같고 기억에서도 빠진것같다(..)


2025-08-12 ⋯ python #2 리스트 vs 제너레이터 비교 실습

1. 100만 개의 숫자 합 구하기 1) 리스트 방식 - numbers=list(range(1000000)) -> sum(numbers) - 0~999,999를 리스트(numbers)로 만들어 합계를 구함 - sys.getsizeof(numbers) - 리스트 객체의 크기를 바이트 단위로 반환 2) 제너레이터 방식 - gen = number_gen() -> sum(gen) - 제너레이터 객체 생성, 내부적으로 하나씩 값을 생성해 합산 - sys.getsizeof(gen) - 제너레이터 객체의 크기를 바이트 단위로 반환 3) 결과 비교 - list(range(1000000)) - list()로 감싸면 메모리에 100만 개 원소의 배열이 만들어지므로 크기가 크다(O(N)). - gen = number_gen() - 제너레이터 객체는 “다음에 뭘 생산할지에 대한 상태”만 저장하고 실제 값(0, 1, 2, …)을 미리 메모리에 올리지 않아서 크기가 작다(O(1)). 2. 짝수의 제곱 총합을 계산 코드 - even_squares_list = [i * i for i in range(N) if i % 2 == 0] → sum(even_squares_list) - 모든 짝수 제곱을 리스트로 생성 후 리스트의 모든 원소를 합산 - even_square_gen(n) → sum(even_square_gen(N)) - 짝수 제곱을 생성하는 제너레이터 함수를 이용해 짝수 제곱을 하나씩 생성하며 합산 결과 - 리스트 방식의 메모리 사용량이 4167352 bytes로 제너레이터의 메모리 사용량 208 bytes보다 컸다. - 리스트 방식의 sum 연산 실행 시간이 0.0649869441986084 초로 제너레이터 방식의 0.10016107559204102 초보다 빨랐다. - 두 방식의 속도 차이는 여러 번 수행 결과 리스트 방식이 빠른 경우도 있었고, 제너레이터 방식이 빠른 경우도 있음. cf) N = 100000000에서의 비교 - N=100000000 (100배)로 수행 결과 리스트의 sum 연산 실행 시간이 8.67517375946045 초로 제너레이터 방식의 6.631064176559448 초보다 느리게 나옴. - 근데 누가 빨리나와야되고 이런건 없다고하심.


2025-08-12 ⋯ python #1 기본문법, 가상환경, 로깅

1. 기본문법 break와 continue의 차이 (p.29) - break - 0부터 9까지 세는 반복문에서 i가 5가 되는 순간 break를 만나면 그 뒤의 숫자는 전혀 세지 않고 반복이 끝난다. - continue - 0부터 4까지 세는 반복문에서 i가 2인 경우 continue를 만나면 2를 출력하지 않고 바로 다음 숫자인 3으로 넘어가고 반복문 자체는 끝나지 않는다. 가변 인자 (p.78) - *args는 인자들을 하나의 튜플로 묶어서 받는다. - **kwargs는 인자들을 하나의 딕셔너리로 묶어서 받는다. - `mix_example(a, b, *args, **kwargs)`일때 `mix_example(1, 2, 3, 4, 5, name="철수", age=30)` - 1과 2는 매개변수 a와 b에 저장 - a:1, b:2 - 3, 4, 5는 args라는 튜플에 저장 - args: (3,4,5) - name="철수"와 age=30은 kwargs라는 딕셔너리에 저장 - kwargs: {'name':'철수', 'age':30} 클로저 (p.86) 1. multiplier(2)를 호출 2. factor가 2로 고정된 multiply 함수가 만들어짐. 3. 이 함수는 나중에 호출해도 2라는 값을 기억하고 있다. 4. double(10)을 하면 10에 2를 곱한 20이 나온다. 2. 가상환경 지금 환경을 그대로 뜨는 방법 (p.109) - pip freeze > requirements.txt를 하면 현재 환경에 설치된 모든 패키지와 그 버전이 기록되고 - 다른 환경에서 똑같은 설정을 만들고 싶다면 pip install -r requirements.txt를 실행하면 된다. .env (p.115) - 데이터베이스 비밀번호나 API 키처럼 코드에 직접 적으면 안 되는 값들은 .env라는 파일에 따로 저장하고 코드에서는 이 파일을 읽어서 사용하는 것이 안전하다. - 사용법 - from dotenv import load_dotenv로 불러오고 - load_dotenv()를 실행하면 .env 파일 안의 값들이 환경 변수로 등록된다. - os.getenv("DB_USER")로 필요한 값을 꺼낼 수 있다. - 깃허브에 올릴때는 gitignore에 넣어야된다. 3. Logging 실습 문제 .env를 위와같이 작성했을때 app.log에 다음 로그 출력하기 - INFO 레벨 메시지: "앱 실행 시작" - DEBUG 레벨 메시지: "환경 변수 로딩 완료" - ERROR 레벨 메시지: ZeroDivisionError 예외 발생 시 출력 코드 결과 app.log


2025-08-11 ⋯ Devops #1 Python 프로젝트 CI/CD & 클라우드 빌드

실습 - 메이크파일, 린팅, 테스트와 같이 파이썬 프로젝트 스캐폴딩에 필수적인 요소가 포함된 깃허브 저장소를 생성해보자. 그리고 간단하게 코드 포매팅을 수행하도록 메이크파일 스크립트를 작성해보자. - 깃허브 액션을 사용하여 두개 이상의 파이썬 버전에 대해 깃허브 프로젝트 테스트를 수행해보자. - 클라우드 네이티브 빌드 서버(AWS 코드빌드, GCP 클라우드 빌드, 애저 DevOps 파이프라인)를 사용하여 지속적 통합을 수행해보자. - 깃허브 프로젝트를 도커 파일로 컨테이너화하고, 자동으로 컨테이너 레지스트리에 새로운 컨테이너가 등록되도록 만들어보자. - locust 또는 loader io와 같은 부하 테스트 프레임워크를 사용하여 애플리케이션에 대한 간단한 부하 테스트 코드를 작성한다. 그리고 스테이징 브랜치에 변경 사항을 푸시할 때 이 테스트가 자동으로 수행되도록 만들어보자. 1. 파이썬 프로젝트 스캐폴딩 + 메이크파일/린팅/테스트 + 포매팅 새 프로젝트 만들기 (로컬) 최소 패키지/테스트 코드 넣기 개발 도구 설치 파일 - ruff(린터+포매터), pytest(테스트), mypy(타입체크)만 사용 메이크파일 작성 (포매팅/린팅/테스트 일괄 실행) 의존성 설치 & 동작 확인 깃허브 저장소 만들고 푸시 2. 여러 파이썬 버전으로 GitHub Actions 테스트 리포지토리 준비 브랜치 생성 워크플로우 폴더 만들기 CI 설정 파일 생성 - 파일 경로: .github/workflows/ci.yml - 내용: 매트릭스로 3.9~3.12 테스트, Makefile 타깃 사용 커밋 & 푸시 PR 생성 - GitHub에서 ci-setup → main으로 Pull Request 생성 - PR이 생성되면 Actions 탭에서 파이썬 3.9/3.10/3.11/3.12 네 개 잡이 병렬로 도는 걸 볼 수 있음. 배지 추가 - README.md에 아래 한 줄 추가(리포지토리 경로는 본인 것으로 교체) 1. Secrets 불필요: 단순 테스트만 하면 깃허브 액션 기본 권한으로 충분. 2. 기본은 ubuntu-latest지만, OS 매트릭스를 늘리고 싶으면 다음과같이 설정 3. Makefile 없이도 가능. 위 Lint/Type/Test 단계를 ruff/mypy/pytest 직접 실행으로 바꿔도 동작. 3. 클라우드 네이티브 빌드 서버로 CI 1. AWS CodeBuild 리포에 buildspec 추가 - 리포 루트에 buildspec.yml 생성 - 여러 파이썬 버전을 돌리고 싶다면 CodeBuild 프로젝트를 버전별로 2~3개 만들거나, Docker 이미지를 바꿔 실행하는 별도 프로젝트를 추가하는 방식이 단순함. CodeBuild 프로젝트 만들기(콘솔) 2. GCP Cloud Build 리포에 cloudbuild.yaml 추가 트리거 연결 cf) cloudbuild.yaml 예시(루트에 있어야 함) cf2) 여러 파이썬 버전으로 돌리기 – 두 가지 방법 1. 스텝을 여러 개 두기 2. 트리거를 2개 만들고, 각각 Substitution으로 버전 넘기기 - 트리거1: _PY_VERSION=3.9 - 트리거2: _PY_VERSION=3.12 - cloudbuild.yaml에서 ${_PY_VERSION} 사용: 3. Azure DevOps Pipelines 리포에 buildspec 추가 - 리포 루트에 azure-pipelines.yml 생성 Azure DevOps 파이프라인 생성 cf) - 만약 GitHub 권한 에러가 나면? - 좌측 하단 Project settings → Service connections → New service connection → GitHub → Grant access(또는 OAuth) → 연결 생성 - 다시 Pipelines → Create pipeline부터 진행 - 이미 리포에 있어야 하는 파일 예시 - azure-pipelines.yml (다중 파이썬 버전 매트릭스)


2025-08-04 ⋯ Docker #5 kubernetes 환경에 나의 앱을 배포해보자

구조 실습과의 차이? 1. cicd.sh를 쓴다. 2. deploy 디렉토리를 쓴다. 3. docker-build.sh와 docker-push.sh에서 amd였던걸 arm으로 바꿔줬는데 이걸다시 amd로 바꿔준다. ~1. cicd.sh 작성 **(불필요)**~ cicd.sh 사용하는 부분이 나오는데 ppt랑 workspace 디렉토리 안에 아무리찾아봐도 없어서... 일단 챗지피티에넣고 만들었는데 막상 뒤에서는 cicd.sh 쓰는대신 그냥 `kubectl apply -f deploy.yaml` `kubectl apply -f service.yaml` 만 해줬다. 2. deploy 디렉토리 deploy 디렉토리의 deploy.t와 service.t는 각각 .sh로 바꿔준다. - deploy.sh에서 amd를 유지해주고 - service.sh는 원래 8080 돼있었는데 8888 아닌가 싶어서 바꿔줬다. env.properties의 CPU_PLATFORM=amd64으로 설정했고 deploy.sh에서 amd64로 해주고 service.sh에서 port: 8888로 변경했다. 3. docker-build.sh와 docker-push.sh 재수정 arm을 amd로 다시바꿔줫다. 마찬가지 arm을 amd로 바꿔줌. 4. Docker 이미지 빌드, 푸시, kubernetes 환경에 배포 ~이렇게 하면 나와야되는데 계속 404 에러 나옴.~ *해결.. ㅠㅠ* 1. deploy.sh: 챗지피티에서 amd64 떼라고해서 마지막엔 `image: amdp-registry.skala-ai.com/skala25a/${USER_NAME}-posts-get:1.0`도 썼다 2. deploy.yaml: 마찬가지로 amd64 떼라고해서 `amdp-registry.skala-ai.com/skala25a/sk019-posts-get:1.0`도 썼다. 3. defalut.conf는 다음 3가지 버전을 시도했다. 4. cicd.sh -y 스크립트가 존재하지 않는 경우에 `kubectl apply -f deploy.yaml`와 `kubectl apply -f service.yaml`로 대체 가능하대서 그냥 패스했는데 그래도 되는게 맞는지 모르겟음 교수님께 질문사항 디엠 보냈는데 > 우선 deploy를 통해 자신이 만들어놓은 컨테이너 이미지를 클라우드 환경으로 잘 배포했습니다. > > 그리고 service를 통해 나의 컨테이너 내 80포트를 노출하고 있는 nginx를 외부에서 접속 가능하도록 잘 연결했습니다. 이것은 어디서든 접속가능하게 하기 위한 ingress 설정이 있는데 이것은 제가 미리 만들어놓아서 위의 URL로 접속됩니다. > > 단지 내가 외부 접속을 위한 ingress 설정에 등록했던 service 이름인 sk019-posts-get이였는데 sk019-posts-get-svc로 만들어 놓아서 이름만 변경해놓았습니다. 라고 오셔서 확인해보니까 말도안되게 service.yaml이 다음과같이 작성돼있었다 아니근데 위 작업 하면서 쓴 챗지피티 대화창에 'sk019-posts-get' 치면 어디서도 'sk019-posts-get-svc'라는 단어가 없는데....... 어디서 나온건지 모르겟음 아무튼 링크를 확인해보니까 잘들어가있다 ㅎㅎ


2025-08-04 ⋯ Docker #4 자신의 Frontend 개발 코드를 컨테이너로 만들고 이것을 실행시켜 보자

1. nginx:alpine 이미지를 사용 2. 노출 Port는80 3. nginx를실행하는방식은 -nginx -g daemon off; 4. nginx의 routing 설정은 default.conf에설정한다. 1. docker-build.sh와 docker-push.sh 복사 docker-build.sh에서 amd였던걸 arm으로 바꿔줫고 docker-push.sh에서 마찬가지 amd를 arm으로 바꿔줌. 2. Dockerfile과 default.conf 작성 원래 코드에 `COPY src/ /usr/share/nginx/html/`이 없었는데 필요한거아닌가 싶어서 넣어줬다. 3. 파일 구조 index.html이랑 이미지 디렉토리 media는 src 디렉토리에 넣었다. 4. 이미지 push build + docker run 마찬가지 run 주소도 arm으로 넣어줌. 잘 나온다 ㅎㅎㅎ 5. 헷갈리는점 1. docker-build.sh에서 - IMAGE_NAME을 healthcheck-server로 바꿔주라고 ppt에 나와있었는데 안바꾸고 webserver를 썼는데 마지막에 `sudo docker run -d --name posts-get --network bridge -p 8888:80 sk019-posts-get.arm64:1.0` 했을때 제대로 나왔다. - 근데 chatgpt 치니까 IMAGE_NAME="healthcheck-server" 해놓고 `sudo docker run -d --name posts-get` 해버리면 안된다고나옴 빌드한 이미지와 실행한 이미지 이름이 다르다고 근데 원래는 달랐는데 잘되던데... 확인 필요할듯. 2. default.conf는 사실 아래 코드로 바꿔넣어줬었다. index.html;을 추가한것임. 이부분도 확인 필요. *1에 추가: env.properties에서 SERVICE_NAME="posts-get"가 나오긴한데 docker-build.sh와 docker-push.sh 가 앞에 source ./env.properties가 붙는 식으로 진행되면 IMAGE_NAME="posts-get"이 적용되고 이미지 이름이 sk019-posts-get:1.0.0으로 만들어지고, run/push 시 모두 일관성이 유지되는게 맞는데? env.properties를 불러오지도 않고, IMAGE_NAME에 healthcheck-server이 하드코딩 대있어서 연관성을찾기 어려운상태.


2025-08-04 ⋯ Docker #3 레지스트리 접속, 이미지 관리

1. 레지스트리에 접속하고 이미지를 pull/push하기 2. Docker Hub에서 우분투 이미지 받아보기 3. Dockerfile : 명령어 CMD 실습 4. 의문점 정리 CMD? Dockerfile에서 CMD는 도커 컨테이너가 실행될 때 자동으로 수행할 기본 명령어를 지정하는 역할인데 예를 들어 `CMD echo "This is the default command"`는 사용자가 docker run 명령을 통해 별도의 명령을 전달하지 않았을 때 이 기본 명령이 실행된다. 그래서 `sudo docker build -t my-image .`로 이미지를 만들고, `sudo docker run my-image`라고 실행하면 `This is the default command`라는 메시지가 출력됨. Override? (`docker run` 명령에 인자를 추가로 넘기면?) docker run 뒤에 인자를 직접 주면 예를 들어 `sudo docker run my-image echo "Overridden command"` 이렇게 실행하면 `CMD ["echo", "This is the default command"]`를 쓰지 않고, `echo "Overridden command"`를 실행한다. 결론적으로 이렇다: 1. `sudo docker run my-image` -> CMD가 그대로 실행됨 (`echo "This is the default command"`) 2. `sudo docker run my-image echo "Hello"` -> CMD는 무시되고 사용자가 입력한 `echo "Hello"`만 실행됨 CMD 작성 형식? `sudo docker run my-image "Override CMD"`처럼 인자를 넘기면 원래 CMD의 `echo` 명령은 유지되고 `"Override CMD"`라는 문자열이 인자로 전달되어 `echo "Override CMD"`가 실행될 것 같지만 Docker가 전달한 문자열을 실행 가능한 명령어로 인식하지 못하기 때문에 오류가난다. 이문장은 `sudo docker run my-image sh -c "echo Override CMD"`처럼 `sh -c`를 통해 쉘 명령어로 감싸주면 `"Override CMD"`가 정상적으로 출력된다. 결론적으로 이렇다: 1. `CMD ["echo", "Hello"]` 이렇게 (명령어와 인자를 분리해서) 써주거나 2. `sudo docker run my-image sh -c "echo Hello"` /bin/sh -c로 감싸서 실행되거나.


2025-08-01 ⋯ Docker #2 작년 작업 복기: netmhcpan image 불러와서 패키지 돌리기

2024.11.24 MutClust 작업중에 netmhcpan을 돌려야되는 상황이 왓었는데 netmhcpan이 유료였나 그래서 패키지 다운은 안되고 담당 박사님은 그만두셧고.. 서버 뒤지다가 위 README 파일 발견해서 결과물 저장까진 했던 기억이있다. 이때먼가 의문이 들었던게 새로운 conda 환경에 접속한거같은 느낌이 아니라 완전 다른 제2의서버에 접속한 느낌이었는데 이상하게 연구실 디렉토리들은 그대로 접근이 가능해서 혼란스럽지만 그냥 절대경로 다 박고 수행했는데 결과들이 문제없이 저장됐었다. 그래서 그뒤로 걍잊어버리고있었는데 docker 배우고나니까 먼가 이해돼서 이해된김에 정리해보기! 2. 도커 이미지 pull 및 수행 1. 도커 이미지 다운로드 당시 chatgpt 응답 > `docker pull * (*: 링크 블라인드 처리)` 명령어는 Docker Hub에서 *의 1.0 버전을 다운로드합니다. 이미지 크기가 953.76 MB이므로, 다운로드에는 네트워크 속도에 따라 몇 분 정도 소요될 수 있습니다. > 2. 도커 이미지 수행 당시 chatgpt 응답 > 이미지 다운로드 후, 다음과 같은 명령어로 Docker 컨테이너를 생성하고 실행할 수 있습니다. 예를 들어, 로컬 시스템의 특정 디렉토리를 컨테이너에 마운트하고 싶다면: > > `docker run -it -v /path/to/local/datac:/data *` > > 이 명령은 다음과 같은 작업을 수행합니다: > > - `it`: 컨테이너를 대화식 모드로 실행하고 터미널 입력을 위한 인터페이스를 제공합니다. > - `v /path/to/local/data:/data`: 호스트 시스템의 `/path/to/local/data` 디렉토리를 컨테이너의 `/data` 디렉토리에 마운트합니다. 이렇게 하면 컨테이너 내부에서 호스트 시스템의 데이터에 접근할 수 있습니다. 당시 내 코드 몰랐는데 호스트 시스템(우리 서버)의 내 작업 디렉토리("/data/home/ysh980101/2411/data-mhc")를 컨테이너에 마운트 했었다. 3. netMHCpan 실행 1. netMHCpan 설치 경로 찾기 당시 chatgpt 응답 > > 컨테이너가 시작되면 기본적으로 `/home/RSEM-1.3.3` 디렉토리에서 시작되는 것 같습니다. 이 디렉토리는 RSEM 관련 작업을 위한 것으로 보이며, netMHCpan 작업을 수행하려면 적절한 디렉토리로 이동해야 할 수 있습니다. > > 컨테이너 내에서 netMHCpan 설치 디렉토리로 이동하려면, 해당 디렉토리의 경로를 알아야 합니다. netMHCpan의 설치 위치를 확인하고, cd 명령어로 그 위치로 이동합니다. > > `cd /path/to/netMHCpan` 당시 내 코드 netMHCpan의 설치 디렉토리 /netMHCpan-4.1 를 찾음. 2. netMHCpan 사용 당시 chatgpt 응답 > 디렉토리로 이동: 이제 확인된 설치 경로로 이동하여 작업을 시작합니다. > > `cd /netMHCpan-4.1` > > 작업 실행: netMHCpan을 실행하려면, 해당 디렉토리 내에서 실행 파일을 찾아 해당 명령을 사용합니다. 예를 들어, 다음과 같은 명령어를 사용할 수 있습니다: > > `./netMHCpan -a HLA-A02:01 -l 9 -f your_peptide_file.txt -o output.txt` > > 이 명령은 HLA-A02:01 앨리얼을 대상으로 하는 9-mer 펩타이드를 your_peptide_file.txt에서 읽어들여 output.txt에 결과를 출력합니다. 당시 내 코드 patient_id.txt에 있는 각 환자 ID에 대해 루프를 돌림 환자별 .csv 파일에서 첫 번째 컬럼(펩타이드 서열)만 추출하고, 헤더를 제거해서 .txt 파일로 만들고 Affinity prediction하려는 hla allele를 HLA_I_ALLELES_FILE에 저장해놧엇는데 HLA_I_ALLELES_FILE에 대해 루프를 돌림. 각 allele마다 NetMHCpan 실행 결과를 allele 이름으로 된 .txt 파일로 저장. 해당 환자의 모든 allele에 대한 결과 파일을 하나의 .csv로 병합. sc.py는? 대충 이런식인데 최종적으로는 Allele,Peptide,Affinity 컬럼 갖는 테이블을 반환. 전체 bash script는 이랬다 4. Docker 종료 그때 노션 보니까 챗지피티가 이런말도 해줫다. > 작업이 완료되면, exit 명령어를 입력하여 컨테이너에서 나올 수 있습니다. 컨테이너를 종료하지 않고 나온 경우, 다음과 같이 컨테이너를 다시 시작하거나 종료할 수 있습니다. > > 컨테이너 재시작: `docker start [container_id_or_name]` > > 컨테이너 내부로 들어가기: `docker attach [container_id_or_name]` 이때 이해를못한상태니깐 exit를 하면 그냥 완전 나가기가 된다고 생각했던거같다. 그래서 한 10번 넘게 들어가서 작업했는데 내가 컨테이너를 하나도 종료안해놔서 한 6개월뒤에 사람들이 ys910111 누구냐고 머라했던기억이 ㅋㅋ ㅠㅠ 그래도 이제 먼가 이해되니깐조은듯.