Dockerfile을 읽을 줄 안다는 것은 해당 이미지가 어떻게 구성되어 있는지 알 수 있다는 의미이다.
Dockerfile 작성 방법
키워드
FROM: 시작점이 될 베이스 이미지를 지정한다.
MAINTAINER: 이미지를 생성한 개발자의 정보를 표시한다. (1.13.0 이후에는 권장되지 않고, LABEL을 사용하는 것이 좋다.)
LABEL: 이미지에 메타데이터를 추가한다. key-value 형태로 지정된다.
RUN: 이미지 내에서 명령어를 실행한다. 실행 결과는 새로운 레이어에 기록되고, 이 레이어는 최종 이미지에 포함된다. 각 RUN 명령은 새로운 레이어를 생성하며, 이러한 레이어들은 캐시되어 재사용될 수 있다.
WORKDIR: 컨테이너 내에서의 작업 디렉토리를 지정한다. 해당 디렉토리가 없으면 자동으로 생성되며, 이후의 작업들은 이 디렉토리 내에서 실행된다.
EXPOSE: 이미지를 실행할 때 열려야 하는 포트를 지정한다. 이 포트는 컨테이너 실행 시 -p 옵션으로 호스트와 매핑될 수 있다.
USER: 컨테이너가 실행될 때 사용될 사용자를 지정한다. (기본값 root)
COPY / ADD: 호스트 시스템의 파일이나 디렉토리를 이미지에 복사한다. ADD는 COPY보다 확장된 기능을 제공한다. ADD는 URL로부터 파일을 가져오거나 압축 해제 기능도 제공한다. 특별한 기능이 필요하지 않은 경우, COPY를 사용하는 것이 권장된다.
ENV: 컨테이너 내에서 사용될 환경 변수를 설정한다.
CMD / ENTRYPOINT: 컨테이너를 생성하거나 실행할 때 수행될 명령어를 정의한다. docker run 명령으로 새 컨테이너를 생성하거나, docker start 명령으로 정지된 컨테이너를 시작할 때 해당 명령어가 실행된다. 주로 컨테이너 내부에서 지속적으로 실행되어야 하는 서버나 애플리케이션을 시작할 때 사용된다.
CMD: CMD는 docker run 실행 시 추가적인 명령어가 주어지면, 해당 명령어를 ENTRYPOINT에 지정된 명령어의 인자로 사용한다. 만약 ENTRYPOINT가 정의되어 있지 않다면, CMD에서 지정한 명령어를 실행한다.
Dockerfile 내에서 CMD를 여러 번 정의할 수 있지만, 마지막으로 정의된 CMD만 최종적으로 사용된다.
CMD는 3가지 형태로 작성될 수 있다: - CMD ["executable", "param1", "param2"]: 실행할 프로그램과 그에 대한 인자를 지정한다. - CMD ["param1", "param2"]: ENTRYPOINT로 지정된 실행 가능한 파일의 인자로 사용된다. - CMD command param1 param2: shell 형태로 명령어를 실행한다. 여기서는 /bin/sh -c를 사용하여 해당 명령어를 실행한다.
ENTRYPOINT : ENTRYPOINT는 docker run 실행 시, 추가적인 명령어가 주어지면 이 명령어는 ENTRYPOINT에 지정된 명령어에 대한 인자로 사용된다. CMD에서 기본 인자가 제공되었을 때 docker run에서 명령어를 추가로 주게 되면, CMD의 인자는 덮어쓰여진다.
ENTRYPOINT 명령은 2가지 형태로 제공된다: - exec 형태: ENTRYPOINT ["executable", "param1", "param2"]. 이 형태는 명령어가 PID 1로 실행되어 신호 처리가 올바르게 이루어진다. - shell 형태: ENTRYPOINT command param1 param2. 이 형태를 사용할 때, 명령어는 실제로 /bin/sh -c 내에서 실행되므로 신호 처리에 주의가 필요하다.
ENTRYPOINT는 CMD와 함께 사용될 때, ENTRYPOINT는 실행할 명령어를, CMD는 그 명령어의 기본 인자로 사용된다.
Docker 빌드
Dockerfile의 기본이름은 Dockerfile이다. 파일명을 이렇게 설정하면 파일이 위치한 경로에서 docker build . 만 해도 빌드가 가능하다. 하지만 이렇게 생성하면 이미지의 이름과 태그가 없기 때문에 -t 옵션으로 태그와 파일명 지정이 가능하다.
docker build . -t container_name:tag
Dockerfile은 파일명 변경이 가능하다. 위에서 파일명을 다르게 지정했다면 -f 옵션으로 지정이 가능하다.
인프라의 규모가 확장되면서 여러 팀원들과 함께 작업하는 상황에서는 동일한 상태 파일을 공유하고 동기화하는 것이 중요해졌다.
https://kschoi728.tistory.com/139
유형 1은 직접 .tf 파일과 tfstate 파일을 공유하는 방식이다. 이런 방식으로 파일을 공유할 수는 있지만, 수동으로 관리하는 데에는 여러 번거로운 작업들이 포함된다. 이 과정에서 인간의 실수나 정보의 누락이 발생할 수 있기 때문에 이러한 문제점을 해결하고자 버전 관리 시스템(VCS)의 도입이 필요해졌다.
유형 2에서는 VCS(예: git, svn)를 사용하여 해당 파일들을 효율적으로 관리한다. 이를 통해 변경 이력을 체계적으로 추적할 수 있으며, 필요한 경우 이전 버전으로 쉽게 롤백할 수 있게 되었다.
유형 3은 git을 사용하여 .tf 파일을 관리하는 동시에, s3와 같은 백엔드 서비스를 활용하여 tfstate 파일을 안전하게 저장하고 관리하는 방식이다. 이 구조를 사용하면 각 파일의 특성에 맞는 최적화된 관리 방식을 선택할 수 있다.
일반적으로는 유형 3의 구조에서 Git 저장소에 브랜치를 생성한 후 PR을 보내서 작업을 진행하게 된다.
PR은 git으로 코드를 관리할 때 주로 사용되는 브랜치 branch를 이용하는 방법으로 작업자가 코드 수정을 위해 새로운 브랜치를 생성하고 작업한 후 본인의 브랜치를 push하여 코드리뷰 후 Merge를 요청하는 방식이다.
Push와 Pull만으로도 코드 협업이 가능하긴 하지만 이렇게 할 경우, 다른 사람이 작성한 커밋을 확인하기가 쉽지 않고 리모트 저장소에 푸시할 때가 되어야 충돌 상황을 확인 할 수 있다. PR을 사용하면 작업된 테라폼 코드에 대한 자연스러운 리뷰와 메인스트림 main branch의 병합을 관리하기 수월해진다.
이전에 만들었던 Terraform VPC Module에 PR을 보내기 위해 feature 브랜치를 생성했다.
브랜치 생성
작업 후에 PR을 생성하고 main 브랜치에 Merge 해보자.
commit 후 pushopen a pull requestcommentmerge
위 과정을 거치면 다른 브랜치를 통해서 작업한 내용을 main브랜치에 합칠 수 있다.
Hashcorp TFC (Terraform Cloud) 백엔드
위에서 VCS와 PR을 활용하는 방법을 확인했다. 하지만 유형3에서 코드의 관리만큼 중요한 상태 파일의 관리를 해결하지 못했는데,
s3 등의 시스템들을 활용하면 백엔드로 관리하면 상태 파일을 안정적으로 공유할 수 있다.
그 중 TFC 백엔드에 대해서 정리해보려고 한다.
하시코프에서 프로비저닝 대상과 별개로 State를 관리할 수 있도록 SaaS 환경인 TFC를 무상 제공한다.
제공 기능 : 기본 기능 무료, State 히스토리 관리, State lock 기본 제공, State 변경에 대한 비교 기능
Free Plan 업데이트 : 사용자 5명 → 리소스 500개, 보안 기능(SSO, Sentinel/OPA로 Policy 사용) -> 링크
# variables.tf
variable "instance_type" {
description = "EC2 instance type"
type = string
}
variable "subnet_id" {
description = "Subnet id to launch the instance in"
type = string
}
variable "security_group_ids" {
description = "List of security group ids to associate with the instance"
type = list(string)
}
variable "instance_name" {
description = "Name to be used on all resources as prefix"
type = string
}
variable "associate_public_ip_address" {
description = "Associate a public ip address with the instance"
type = bool
default = false
}
# main.tf
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["099720109477"] # Canonical
}
resource "aws_instance" "this" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
subnet_id = var.subnet_id
vpc_security_group_ids = var.security_group_ids
associate_public_ip_address = var.associate_public_ip_address
tags = {
Name = var.instance_name
}
}
# output.tf
output "instance_id" {
description = "The ID of the instance"
value = aws_instance.this.id
}
테라폼은 terraform 바이너리 파일을 시작으로 로컬 환경에나 배포 서버와 같은 원격 환경에서 원하는 대상(프로바이더가 제공하는 API)을 호출하는 방식으로 실행된다. 각 프로바이더의 API 구현은 서로 다르지만 테라폼의 고유 문법으로 동일한 동작을 수행하도록 구현되어 있다.
https://malwareanalysis.tistory.com/619
다수의 프로바이더를 로컬 이름 지정
required_providers 블록을 사용하여 다수의 프로바이더를 로컬 이름으로 지정해 사용할 수 있다.
예를 들면 아래는 여러 프로바이더가 제공해주는 동일한 http 관련 data 블록을 사용하는 예제이다.
기본 유형에는 string, number, bool, any가 있고, 집합 유형에는 list, map, set, object, tuple이 있다.
변수 정의할 땐 아래 메타인수가 사용 가능하다.
default : 값이 전달되지 않은 경우의 기본 값, default가 없으면 apply 시 변수에 대한 정보를 물어봄
type : 변수 유형 정의, string number bool list map set object tuple 와 유형을 지정하지 않으면 any 유형으로 간주
description : 입력 변수의 설명
validation : 변수 선언의 제약조건을 추가해 유효성 검사 규칙을 정의
sensitive : 민감한 변수 값으로 테라폼의 출력문에서 값 노출을 제한함 (암호 등 민감 데이터의 경우)
nullable : 변수에 값이 없어도 됨 (null 허용 여부)
사용 예시는 아래와 같다.
variable "string" {
type = string
description = "var String"
default = "myString"
}
variable "number" {
type = number
default = 123
}
variable "boolean" {
default = true
}
variable "list" {
default = [
"google",
"vmware",
"amazon",
"microsoft"
]
}
output "list_index_0" {
value = var.list.0 # google, 1이면 vmware
}
output "list_all" {
value = [
for name in var.list : upper(name)
]
}
variable "map" { # Sorting
default = {
aws = "amazon",
azure = "microsoft",
gcp = "google"
}
}
variable "set" { # Sorting
type = set(string)
default = [
"google",
"vmware",
"amazon",
"microsoft"
]
}
variable "object" {
type = object({ name = string, age = number })
default = {
name = "abc"
age = 12
}
}
variable "tuple" {
type = tuple([string, number, bool])
default = ["abc", 123, true]
}
variable "ingress_rules" { # optional ( >= terraform 1.3.0)
type = list(object({
port = number,
description = optional(string),
protocol = optional(string, "tcp"),
}))
default = [
{ port = 80, description = "web" },
{ port = 53, protocol = "udp" }]
}
variable "my_password" {
default = "password"
sensitive = true
}
우선 순위 :
변수가 선언되는 방식에 따라 우선순위가 존재한다.
실행 후 입력 < variable 블록의 default 값 < 환경 변수 (TF_VAR 변수 이름) < terraform.tfvars < *.auto.tfvars < *.auto.tfvars.json < CLI 실행 시 -var 인수에 지정 또는 -var-file로 파일 지정
오른쪽으로 갈수록 우선순위가 높다. 예를들어 변수의 값을 출력해주는 코드가 있을 때, terraform.tfvars 내부 변수에 1이라는 값이 정의되어 있고, 실행후에 3이라는 값을 입력 한다면 결과는 1이 나오게 된다.
HashiCorp사의 Terraform은 버전화, 재사용 및 공유할 수 있는 사람이 읽을 수 있는 구성 파일에서 클라우드 및 온프레미스 리소스를 모두 정의할 수 있는 코드형 인프라(IaC) 도구로 Amazon Web Services(AWS), Azure, Google Cloud Platform(GCP), Kubernetes, Helm, GitHub, Splunk, DataDog 등 수천여개의 다양한 Provider를 제공한다.
Provider
테라폼을 적용할 대상으로 한번에 하나의 프로비저닝만 가능하며 terraform init을 통해 설치된다. (aws 구성을 azure로 변경하는 등의 작업은 불가함, 프로바이더에 대한 자세한 정보는 아래 링크 참조)
원하는 프로바이터를 선택하고 오른쪽 상단의 USE PROVIDER 버튼을 누르면 간단한 사용방법이 나온다.
Workflow and Terminology used in Terraform
테라폼은 크게 write -> plan -> apply의 과정을 거쳐 인프라를 프로비저닝한다.
write :여러 클라우드 공급자 및 서비스에 걸쳐 있을 수 있는 리소스를 정의한다.
plan : 기존 인프라 및 구성(tfstate)을 기반으로 생성, 업데이트 또는 파괴할 인프라를 설명하는 실행 계획을 생성한다.
apply :승인 시 Terraform은 모든 리소스 종속성을 고려하여 올바른 순서로 제안된 작업을 수행한다.
https://mindmajix.com/terraform-tutorial
아래는 테라폼에서 사용하는 용어들이다.
resourece : 실제로 생성할 인프라 자원을 의미
output : 인프라를 프로비저닝 한 후에 생성된 자원을 output 부분으로 출력할 수 있음
backend : terraform의 상태를 저장할 공간 지정 (내부 / 외부 모두 가능 , 이 기능으로 다른사람들과 협업 가능)
module : 공통적으로 활용할 수 있는 인프라 코드를 한곳으로 모아 정의하는 부분 (변수만 바꿔서 동일한 리소스를 쉽게 생성 가능)
remote state : VPC , IAM 등과 같이 여러 서비스가 공통으로 사용하는 것이 가능 ( ftstate 파일이 저장돼 있는 backend 정보를 명시하면, trerraform이 backend에서 output 정보들을 가져옴 )
.tfstate 파일 : 테라폼이 인프라스트럭처의 상태를 추적하고 관리하는 핵심 도구로 아래와 같은 역할을 수행한다.
인프라스트럭처 상태 추적: 파일을 통해 프로비저닝된 리소스의 상태를 기록하고, 변경 사항을 추적하며, 이전 상태와의 차이를 분석하여 필요한 변경을 식별합니다. (plan 시 달라지는 부분을 확인할 수 있음)
변경 관리: 변경 사항을 .tfstate 파일에 반영하고 apply 시 이 파일을 사용하여 변경 사항을 적용한다. 이런 방식으로 인프라스트럭처를 안전하게 변경하고 일관성 있게 유지할 수 있다.
협업 및 공유: .tfstate 파일은 팀 간 협업과 공유를 지원한다. 팀원들은 동일한 .tfstate 파일(백엔드 등)에 액세스하여 상태를 공유하고, 변경 사항을 추적하고 충돌을 방지할 수 있다.
롤백 및 복구: .tfstate 파일은 변경 사항을 롤백하고 이전 상태로 복구하는 데 사용될 수 있다.
환경 구축
테라폼 설치하기
테라폼은 여러 운영체제에서 사용할 수 있지만, Mac을 사용하기 때문에 brew와 tfenv를 사용해서 설치해보려고 한다.
ftenv를 사용하면 테라폼의 버전을 쉽게 관리할 수 있다.
# tfenv 설치
brew install tfenv
# 테라폼 설치
tfenv list-remote # 설치 가능 버전 확인
tfenv install 1.5.1
tfenv use 1.5.1
tfenv list # tfenv로 설치한 버전 확인
# 테라폼 버전 정보 확인
terraform version
C++ 17로 윈도우에서 프로그램을 만들면서 폴더와 파일을 구분해야하는 경우가 생겨 아래 코드를 찾게되었다.
찾은 코드는 테스트 시 작동에는 문제가 없었지만 문자열을 LPCWSTR (WCHAR *)로 받아야 한다는 사소한 단점이 있었다. 혹시 String 클래스를 사용해 입력받은 경로가 폴더인지 파일인지 구분할 수 있는 방법이 있는지도 찾아봤지만, 검색을 해봐도 찾지 못했다..
int isFolder(LPCWSTR path) {
/*
args:
LPCWSTR fileName : 파일의 full path
return:
int code : 폴더는 1, 파일은 0, 에러는 -1
summary:
폴더인지 파일인지 구분
*/
WIN32_FIND_DATA wInfo;
HANDLE hInfo = ::FindFirstFile(path, &wInfo);
::FindClose(hInfo);
if (hInfo != INVALID_HANDLE_VALUE)
{
if (wInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
return 1;
}
return 0;
}
return -1;
}
구글 검색을 통해 C++로 작성된 HTTP 클라이언트/서버 애플리케이션을 개발할 때 도움를 주는 C++ REST SDK (또는 Casablanca) 라이브러리를 찾았다.
해당 라이브러리는 microsoft의 공식 라이브러리로 깃 허브에 올라와 있었기 때문에, 빌드를 위해 아래 Dockerfile을 작성했다.
casablanka.Dockerfile
FROM ubuntu:latest AS env
RUN apt-get update
RUN apt-get upgrade -y
RUN apt-get install -y sudo
RUN apt-get install -y g++ git libboost-atomic-dev libboost-thread-dev libboost-system-dev libboost-date-time-dev libboost-regex-dev libboost-filesystem-dev libboost-random-dev libboost-chrono-dev libboost-serialization-dev libwebsocketpp-dev openssl libssl-dev ninja-build
RUN git clone https://github.com/microsoft/cpprestsdk.git
RUN apt-get install -y cmake zlib1g-dev
WORKDIR cpprestsdk
RUN mkdir build && cd build
RUN cmake . -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=1 -DCMAKE_CXX_FLAGS="-Wno-error" -DCMAKE_CXX_FLAGS="-Wno-format-truncation"
RUN make -j$(nproc)
RUN sudo make install
위 코드를 사용해 docker로 빌드하면 .so 파일들이 나온다.
하지만 지금은 윈도우 용 프로그램을 만들고 있기 때문에 visual studio에서 사용할 수 있도록 설정을 다시해봐야 할 것 같다.
request와 json 패키지를 사용해서 JSON 데이터를 파싱할 때, 각 키의 데이터를 수정하려면 아래처럼 dictionary 형태로 변환시켜 value를 수정한다.
data = {"key1":"value1"}
data["key1"] = "value2"
전체 데이터를 스캔하여 특정 값을 가진 키를 찾으려고 한다. 아래 코드로 (for, if로) 전체를 확인하면서 원하는 값을 가진 키를 찾았다.
data = {
"key1":"value1",
"key2":"value2",
"key3":"value3",
"key4":"value4",
"key5":"value5",
}
for key in data:
if "value3" == data[key]:
print(key)
break
간단한 데이터라면 위 방식으로도 충분히 찾을 수 있다. 하지만 데이터의 수가 매우 많거나 복잡할때도 같은 방식으로 값을 찾고 수정할 수 있을까?
아래처럼 사람의 정보를 가진 JSON 데이터에서 "-", "N/A", "" 의 값을 가진 키를 찾아서 제거하려고 하는데 각 자료형도 다르고 형태도 일정하지 않다. 사람 수도 매우 많다고 가정하겠다.
먼저 위 형태로 랜덤한 데이터가 원하는 만큼 생성되도록 함수를 만들었다. 아래 이미지를 보면 패턴이 있긴하지만 데이터가 잘 생성된거 같다.
def create_random_data(num:int) -> dict:
import random
data={}
random_string = lambda x: "".join([chr(random.randint(97, 122)) for tmp in range(x)])
for i in range(num):
key = f"person_{i+1}"
data[key] = {
"name":{"first":random_string(random.randint(0, 8)), "middle":random_string(random.randint(0, 8)), "last":random_string(random.randint(0, 8))},
"age":random.randint(10, 50),
"address":"-",
"education":{"highschool":random_string(random.randint(0, 8)), "college":"", "Universitry":random_string(random.randint(0, 8))},
"hobbies":["coding", "-", "", random_string(random.randint(0, 8))],
}
return data
print(create_random_data(5))
이제 파싱을 위한 함수를 만들어야하는데 위 데이터의 형태를 봤을 때 가장 문제가 될 것 같은 부분은 데이터의 깊이라고 생각했다. 데이터의 깊이에 상관없이 탐색할 수 있도록 재귀를 사용해 파싱 함수를 작성해보자.
def clean(data):
remove_keyword = ["N/A", "-", ""]
for key in list(data):
value = data[key]
if type(value) == dict:
clean(value)
if value == {}:
data.pop(key)
if type(value) == list:
for x in [keyword for keyword in remove_keyword if keyword in value]:
for _ in range(value.count(x)):
value.remove(x)
if type(value) == str:
if value in remove_keyword:
data.pop(key)
datas = create_random_data(5)
print(datas)
clean(datas.copy())
print(datas)
나름 잘 정리 됐다. 그런데 JSON 데이터 100만개를 처리하면 시간이 얼마나 걸릴지 궁금해져서 코드를 좀 더 수정해봤다. https://hwan001.co.kr/178 글에 함수의 실행 시간을 측정하는 데코레이터가 있다. 재귀를 사용한 clean함수는 따로 작성해야겠지만 해당 코드를 사용해서 100만 건의 생성 시간과 정리 시간을 측정해보자.
def my_decorator(func):
def wrapped_func(*args):
import time
start_r = time.perf_counter()
start_p = time.process_time()
ret = func(*args)
end_r = time.perf_counter()
end_p = time.process_time()
elapsed_r = end_r - start_r
elapsed_p = end_p - start_p
print(f'{func.__name__} : {elapsed_r:.6f}sec (Perf_Counter) / {elapsed_p:.6f}sec (Process Time)')
return ret
return wrapped_func
@my_decorator
def create_random_data(num:int) -> dict:
import random
data={}
random_string = lambda x: "".join([chr(random.randint(97, 122)) for tmp in range(x)])
for i in range(num):
key = f"person_{i+1}"
data[key] = {
"name":{"first":random_string(random.randint(0, 8)), "middle":random_string(random.randint(0, 8)), "last":random_string(random.randint(0, 8))},
"age":random.randint(10, 50),
"address":"-",
"education":{"highschool":random_string(random.randint(0, 8)), "college":"", "Universitry":random_string(random.randint(0, 8))},
"hobbies":["coding", "-", "", random_string(random.randint(0, 8))],
}
return data
def clean(data):
remove_keyword = ["N/A", "", "-"]
for key in list(data):
value = data[key]
if type(value) == dict:
clean(value)
if value == {}:
data.pop(key)
if type(value) == list:
for x in [keyword for keyword in remove_keyword if keyword in value]:
for _ in range(value.count(x)):
value.remove(x)
if type(value) == str:
if value in remove_keyword:
data.pop(key)
datas = create_random_data(1000000)
print(len(datas), datas, "\n")
import time
start_r = time.perf_counter()
start_p = time.process_time()
clean(datas.copy())
end_r = time.perf_counter()
end_p = time.process_time()
elapsed_r = end_r - start_r
elapsed_p = end_p - start_p
print(f'{"clean"} : {elapsed_r:.6f}sec (Perf_Counter) / {elapsed_p:.6f}sec (Process Time)')
print(len(datas), datas)
데이터가 길어서 다 나오지 않았기 때문에 키워드 개수와 데이터 일부를 찍어봤다.(키워드는 줄지 않음)
생성정리
생성에 129. 3초, 정리에 13초가 걸렸다. 100만 개 정도부터는 확실히 탐색 속도가 느린게 느껴진다. 추가로 예시의 데이터에서는 깊이가 얕아서 재귀로도 문제없이 동작했지만 만약 깊이가 매우 깊다면 탐색 중에 메모리 에러가 발생된다. 재귀가 아닌 큐나 스택을 활용한 방식으로 변경해야 하고 실제로 사용하려면 좀 더 효율적인 알고리즘이 필요할 거 같다.
기본적인 내용이나 활용 등 어느정도 알고 있다고 생각한 항목은 내용을 작성했고, 애매하게 알고있다고 생각한 내용은 주황색, 잘 모른다고 생각한 항목은 빨간색으로 강조했다.
Learn a Programming Language
c : 포인터, 구조체, 함수 등 기본적인 개념 이해 : Windows API 활용 및 DLL 작성 가능
c++ : 클래스, 템플릿, STD 등 기본 문법 활용이 가능
Python : 기본 문법 및 자료 구조 활용 능숙 : 리스트 컴프리헨션, 람다 함수, 데코레이터, 비동기 활용 가능 : 다양한 패키지 활용 경험
Ruby : 모름
Go : 기본 문법 및 고루틴 이해 : 예제 수준 작성 가능
Rust : 모름
JavaScript/Node.js : 기본적인 JavaSctript 및 Ajax 작성 가능 : 간단한 Node.js 백엔드 구축 및 기동 가능
Understand difference OS concepts
Networking : 라우터, 스위치, 허브 개념 : OSI 7 Layer 이해 : 랜, 구리,광케이블 융착 가능 : 채널먹스, MSPP 운용 경험 : TCP/ UDP 개념 이해
Sockets : c, Python 소켓 프로그래밍 가능 : Raw 소켓 테스트 수준으로 가능
I/O Management :
Virtualization : 하이퍼바이저, VM?
Memory/ Storage :
File Systems :
POSIX Basics :
Process management :
Startup management (initd) :
Service management (systemd) :
Threads and Concurrency :
Operating System
Linux (Debian, SUSE Linux, Fedora, Ubuntu, CentOS, Rocky, RHEL) : rpm build.spec 수정/작성 가능, yum repository 구축 가능, rpm/ deb 버전 별 설치 가능 : 서비스, 네트워크 인터페이스, NTP, locale, iptables 등 명령어 능숙 : 유저별 그룹관리, 계정 생성 및 ssh 연동, 자원 관리 : Clonezilla 활용 가능 : 쉘 스트립팅 가능
Unix (FreeBSD, OpenBSD, NetBSD) : OpenBSD만 설치 해봄
Windows : PowerShell 스크립트 가능 (COM 포트 시리얼 통신 등) : Windows API 활용 가능 : DLL injection, Global hooking 코드 작성 경험 있음 : 커널 디버깅 환경 구축 가능 (windbg, 가상 머신) : 필터/HID 드라이버 기초 개념 이해 및 예제 수준 작성 가능
Windows Server : 설치 및 IIS 서버 구축, 사설 ssl 인증서 발급 가능
Learn to live in Terminal
Bash Scripting
Vim/ Nano/ PowerShell/ Emacs
Compiling apps from source : gcc
System Performance : nmon, iostat, sar, vmstat
Others : strace, dtrace, systemtap, uname, df, history
제텔카스텐은 독일어로 메모를 뜻하는 제텔과 상자라는 뜻의 카스텐을 합쳐서 만든 단어로, 독일 빌레펠트 대학 사회학 교수인 니클라스 루만이 개발한 지식 관리 시스템이다. 니클라스 루만 교수는 잘 정리된 제텔카스텐 체계를 활용해 약 30년 간 9만 개의 메모를 작성했고, 350편의 논문과 58권의 책을 집필했다고 한다.
블로그나 노션에 내용을 정리하다보면 카테고리를 분류하기 어려운 경우가 종종 있다. 예를들면 Python과 MongoDB 연동하기, Ubuntu에 Docker 설치하기 등 2가지 이상의 주제가 한 개의 글에 겹칠 때인데 블로그나 노션, 책과 같은 일반적인 글들은 목차와 범주가 있어 트리 구조로 글이 분류되어 있기 때문이다. 하지만 제텔카스텐은 원자화된 메모(노드)간의 연결을 통해서 그래프 형태로 관리되기 때문에 Python과 MongoDB, Ubuntu와 Docker처럼 각 주제를 메모하고 각 주제를 통합하는 메모를 작성하고 연결을 지정하여, 직접 분류하지 않아도 서로 연관되는 메모들끼리 모이도록 관리한다. 일종의 군집화나 수동으로 학습시키는 인공지능이라고 생각할 수 있을 것 같다. 내용이 충분하게 축적되어 있고 구조가 복잡할수록 제 2의 뇌라고 불릴만큼 효과적인 체계가 된다.
제텔카스텐을 제대로 구축하려면 지켜야할 기본적인 원리들이 있다.
원자성 : 각 노트에는 하나의 아이디어만 기록한다.
자율성 : 각 노트는 관련있는 다른 노트로 이동, 처리, 분류하고 연결할 수 있어야 한다.
항상 다른 노트와 연결하기 : 새로운 노트는 기존 노트와 반드시 연결해줘야 한다.
연결 이유 설명하기 : 다른 노트와 연결되는 이유를 항상 설명해야한다.
자신의 언어를 사용하기 : 작성할 내용을 내가 이해하고 나만의 언어로 다시 적어야 한다.
참고 자료 보관하기 : 자료의 정보와 출처를 기록한다.
나의 생각 기록하기 : 아이디어가 떠오를 때 원칙을 지켜 작성한다.
직접 분류하지 않기 : 카테고리를 직접 만들지 말고 연결에 더 신경써야 한다.
연결 노트 만들기 : 연관성이 없어 보이는 노트의 연관성을 찾았을 때, 노트를 설명해줄수 있는 연결 노트를 만든다.
목차 만들기 : 노트가 특정 주제로 통합되는 경우는 목차 노트를 만들어서 서술, 논쟁할 수 있도록 순서를 배치한다.
노트 삭제 절대 금지 : 오래된 노트를 삭제하지 말고 왜 문제인지 설명하는 새로운 노트와 연결한다.
과감하게 노트 만들기 : 사용하지 않는 노트가 작성된다고 하더라고 시스템에는 문제가 없다.
옵시디언은 노션과 같은 마크다운 에디터이다. 다양한 플러그인들과 연동할 수 있고 디스코드나 카카오 오픈 채팅 등 국내 사용자 커뮤티니가 존재한다. 옵시디언의 장점이자 단점은 로컬에서 작동된다는 점인데, 메모가 클라우드에 저장되는 노션과 달리 로컬에만 저장된다는 점은 장점이지만 인터넷을 통한 접근이 어렵다는 것과 백업, 동기화 등의 작업이 따로 필요하다는 점은 단점이라고 볼 수 있을 것 같다. 템플릿을 지정하여 노트의 양식을 정의해둘 수 있고 그래프 뷰 기능이 있어 메모간의 연결 구조를 UI로 볼 수 잇다. 제텔카스텐 기법을 적용하기에 적합한 지식 관리 툴이라고 생각한다.
아래는 옵시디언 사이트에 접속하면 가장 먼저 보이는 문구이다.
오픈소스는 아니지만 개인이 사용할 경우 무료이고 동기화 기능은 매월 10달러 정도의 비용이 필요하다. 옵시디언을 다루는 내용은 직접 툴을 사용해보고 추가로 작성할 예정이다. 다운로드는 아래 링크에서 할 수 있다. https://obsidian.md/
여러 개 서버의 경우로 우측 check 버튼을 통해 체크할 경우 다양한 현재 상태가 출력된다.
여러 개 서버를 각자 체크하기
주기적인 상태 체크
위에서 체크 기능이 잘 동작하는걸 봤지만 프로그램이 내가 원하는 역할을 수행하게 하려면 입력한 정보를 일정 주기로 반복하여 체크할 수 있어야 한다. Repeat Check는 Timer를 활용하여 일정 주기(interval)로 ssh 연결을 시도한다.
후기
간단한 기능을 제공하는 서버긴하지만 확장할 수 있는 내용들이 많다.
프론트에서는 html, css, js 파일을 분리하여 관리/ 확장성을 높이거나 React.js를 활용하여 더 깔끔하고 모던한 웹 사이트로 개선할 수 있고, 백엔드에서도 보안(openssl, oauth)과 디자인 패턴(MVC)을 적용하고 NoSQL DB와 연동해 볼 수 있을 듯 하다. 또한 DevOps 관점에서는 컨테이너화 시켜 쿠버네티스에 배포하고 GitOps를 적용해볼 수도 있다.
퇴근 후와 주말에만 가끔식 하는 프로젝트라 진행은 더디지만 하나씩 공부해서 Pluto에도 적용해보겠다.
FastAPI는 Python 3.6이상의 API를 빌드하기 위한 빠르고 현대적인 웹 프레임워크이다. 웹 부분을 위한 Starlette와 데이터 부분을 위한 Pydantic 덕분에 NodeJS와 Go에 대등할 정도로 높은 성능을 가지고 있으며, 빠른 코드 작성이 가능하다. 직관적이고 쉬운 코드로 인해 휴먼 에러가 약 40%(내부 개발팀 테스트 기준) 감소했다고 한다. 또한 API에 완전히 호환되는 개방형 표준을 기반으로 하여 Swagger(OpenAPI)와 json Schema를 지원한다.
개인적으로는 Flask와 문법이 비슷하지만 기본적으로 추가된 기능이 많고 Django보다는 자동화(?)는 덜 되었지만 훨씬 가볍고 빠르다는 느낌을 받았다.
공식 문서에서는 위 명령어로 방법을 설명하지만, pip로 "uvicorn[standard]"을 install 하면 에러가 발생하고 명령어로 쳐봐도 당연히 실행되지 않는다. 그래서 아래처럼 python 코드 상에서 실행하는 방법을 찾았다. 실행에 문제가 없고 디버깅 측면에서도 더 좋다고 생각한다.