728x90
반응형

아키텍처

마스터 노드

  • 전체 클러스터를 컨트롤하고, 전체 클러스터 상태를 관리함
  • 새로운 파드들의 스케일링, 노드/파드들의 헬스 모니터링, 수요에 따른 어플리케이션 스케일링 등 전체 클러스터에 대한 결정권을 갖는다.
  • 주요 컴포넌트
    • API Server
      • 클러스터와ㅏ 유저/컴포넌트간의 소통을 가능하게 하는 API를 노출시켜준다.
    • etcd
      • 클러스터의 모든설정 데이터를 key-value 값들이 저장된다
      • 모든 클러스터 상태 정보가 여기 저장된다.
    • Controller Manager
      • API Server를 통해 클러스터 상태를 체크하고 원하는 상태가 유지하기 위한 행동을 취한다.
    • Scheduler
      • 새로운 파드들을 리소스 필요량과 가용성에 따라 노드들에 배정한다.
      • 이를 통해 worker 노드들에 workload들을 공평하게 분배한다.

워커 노드

  • 컨테이너(파드)가 스케쥴되고 구동되는 머신을 의미
  • 클러스터의 data plane을 형성하고, 실제 workload를 실행한다.
  • 워커 노드 주요 컴포넌트
    • kubelet
      • 각각의 워커 노드를 구동시키고 Master Node와 통신하는 역할
      • 파드 명세에 적혀있는 대로 컨테이너가 구동되고 그 상태가 건강하게 하는 역할
    • Container Runtime
      • Docker나 containerd와 같은 복수개의 container runtimes를 지원한다
      • 컨테이너 이미지를 가져오고, 컨테이너를 구동하는 역할
    • Kube Proxy
      • 클러스터 내에서 네트워크 소통을 하는 역할
      • 서비스 네트워크 라우팅을 관리하고 Load Balancing을 수행한다.

상호작용 방식

  • Master node와 Worker Node들은 API Server를 통해 서로 통신한다.
  • 유저와 다른 컴포넌트들 역시 API Server를 통해 서로 상호 작용함
  • 예시
    • 새로운 어플리케이션 배포 시, 설정 파일들이 API Server를 통해 전달됨
      • 그 설정들은 etcd에 저장됨
    • Controller Manager는 API Server를 통해 클러스터 상태를 모니터링함
    • 새로운 파드가 스케줄됐을 때, Scheduler가 적정한 Worker Node를 선정한다
      • 이 선정된 내용을 API Server가 선정된 노드에게 전달한다
      • 그리고 그 노드의 kubelet이 컨테이너를 실행시킨다

728x90
반응형
728x90
반응형
  • 대규모 클러스터 환경에서 컨테이너화된 어플리케이션을 자동으로 배포/확장/관리하는데 필요한 요소들을 자동화하는 플랫폼
    • 코드 기반 클러스터 운영
    • 의도한 상태를 유지하며 클러스터를 관리함
  • Kubernetes: 조타수라는 뜻
  • 컨테이너 오케스트레이션 표준이라 여겨짐
  • 구글에서 2014년 오픈 소스로 공개
  • 여러 대의 도커 호스트를 하나의 클러스터로 만들어 줌
  • 다른 오케스트레이션 툴보다 다양한 기능을 제공하기 때문에 더 어려움
  • 배포, 스케일링, 컨테이너 어플리케이션 관리 자동화하는 컨테이너 오케스트레이션 플랫폼
  • 최소 2기가 램 이상을 사용하고, 2CPU 이상을 사용
    • 아니면 kubernetes로 서버 리소스를 다 써버릴 수 있음

특징

  • 코드 기반 클러스터 운영
    • 동일 코드 기반의 의사소통 -> 효율적
      • YAML 형식으로 파드들 및 기타 컴포넌트들을 정의함
      • 이전에는 다같이 검토 가능한 공통 도구가 없었음
  • 의도한 상태 기준 관리
    • 최초 의도한 상태와 현재 실행중인 상태를 쿠버네티스 컨트롤러가 자동으로 확인 (go 의 watch 모듈)
      • 차이점 발견 시, 현재 상태를 자동으로 처음 의도 상태로 변경
      • 즉, 실행중인 컨테이너가 예상치 않게 종료되면 자동으로 새로운 pod 생성

이후에 쿠버네티스 아키텍처와 쿠버네티스 옵션에 대한 글을 써 보고자 한다.

728x90
반응형
728x90
반응형
  • 본 내용은 과거 번역 파일 정리 자동화 프로그램 작업에 대한 업무 일지이다.

Properties 파일 구조

  • properties 파일: <단어> = <번역된 단어> 형태로 관리됨
  • 기능에 따라 폴더가 구분되어있음. 이 폴더명을 back/<폴더명> 명으로 사용
    • 파일 위치 경로: /assets/backend/
  • ko.properties, en.properties로 파일이 나뉘어져 있다.
  • 두 파일은 같은 key를 공유하고 있기 때문에, 하나의 시트에 합쳐서 보여질 수 있게 한다.
    • key, ko, en 으로 칼럼을 잡았다.

코드

  • 번역 파일이 저장되어 있는 디렉토리 내의 파일들 조회
package read

// Import 내용들


// 디렉토리 내의 파일/폴더들 리스트 조회
func ReadDir(dir string) []os.DirEntry {
    entries, err := os.ReadDir(dir)

    if err != nil {
        log.Fatalf("Failed to Read Dir Error: %v", err)

        return nil
    }

    return entries
}

// 파일 읽기
func ReadFile(fileDir string) ([]fs.DirEntry, string) {

    fileList, readErr := os.ReadDir(fileDir)

    if readErr != nil {
        log.Printf("Can't Read json file: %v", readErr)
        return nil, fileDir
    }

    return fileList, fileDir
}
  • Property 파일 내의 데이터 처리하여 저장할 수 있게 가공
package read

// Import 내용들

type PropertyStruct struct {
    Code string
    Ko   string
    En   string
}

func ReadProperties(fileList []fs.DirEntry, pwd string) []PropertyStruct {
    var propertyDataDataList []PropertyStruct

    for _, file := range fileList {
        fileName := file.Name()

        fileNameWithoutFormat := strings.Split(fileName, ".properties")[0]

        if fileNameWithoutFormat == "example" {
            continue
        }

        fileNameLength := len(fileNameWithoutFormat)
        langCode := fileNameWithoutFormat[fileNameLength-2 : fileNameLength]

        if langCode == "en" || langCode == "ko" {
            // 파일 열기
            file, err := os.Open(pwd + "/" + fileName)
            if err != nil {
                log.Printf("[Properties] data Open Error: %v", err)
            }

            defer file.Close()
            // 스캐너로 파일 한 줄씩 읽기
            scanner := bufio.NewScanner(file)

            for scanner.Scan() {
                line := scanner.Text()

                // 빈 줄 또는 주석(#, ;) 무시
                if strings.TrimSpace(line) == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") {
                    continue
                }
                // key=value 포맷으로 나누기
                parts := strings.SplitN(line, "=", 2)
                if len(parts) == 2 {
                    key := strings.TrimSpace(parts[0])
                    value := strings.TrimSpace(parts[1])

                    if langCode == "ko" {
                        convertedString, convTerr := utils.DecodeUnicodeEscapes(value)

                        if convTerr != nil {
                            log.Printf("Convert To Utf8 Err: %v", convTerr)
                        }
                        value = convertedString
                    }

                    isFound := false

                    for i := range propertyDataDataList {
                        data := &propertyDataDataList[i]

                        if data.Code == key {
                            isFound = true

                            switch langCode {
                            case "en":
                                data.En = value
                            case "ko":
                                data.Ko = value
                            }
                            break // 값을 찾았으므로 루프를 종
                        }
                    }

                    if !isFound {
                        propertyData := PropertyStruct{Code: key} // 새 구조체 생성
                        switch langCode {
                        case "en":
                            propertyData.En = value
                        case "ko":
                            propertyData.Ko = value
                        }
                        propertyDataDataList = append(propertyDataDataList, propertyData)
                    }

                }
            }

            if err := scanner.Err(); err != nil {
                log.Printf("Scan Error: %v", err)
            }
        }

    }

    return propertyDataDataList
}
  • 처리한 데이터들을 받아 액셀파일 생성
package write

// Import 내용들


type FileWork struct {
    file        *excelize.File
    FileName    string
    SheetLength int
}

// 칼럼 세팅
func (excelFile *FileWork)HandlePropertiesColumns () {
    excelFile.file.SetCellValue(sheetName, "A1", "Code")
    excelFile.file.SetCellValue(sheetName, "B1", "Ko")
    excelFile.file.SetCellValue(sheetName, "C1", "En")
}

// 데이터 레코드 입력
func (excelFile *FileWork)HandlePropertiesRows(dataList []read.PropertyStruct) {
    for i := 0; i <= len(dataList)-1; i += 1 {
        excelFile.file.SetCellValue(sheetName, fmt.Sprintf("A%d", i+2), dataList[i].Code)
        excelFile.file.SetCellValue(sheetName, fmt.Sprintf("B%d", i+2), dataList[i].Ko)
        excelFile.file.SetCellValue(sheetName, fmt.Sprintf("C%d", i+2), dataList[i].En)
    }
}

// 액셀 파일 저장
func (file *FileWork) SaveExcelFile() {
    savingErr := file.file.SaveAs(file.FileName)
    if savingErr != nil {
        log.Printf("Saving File Error: %v", savingErr)
    } else {
        log.Printf("Saving Success: %s", file.FileName)
    }
}

func OpenSaveExcelWithProperties(sheetName string, index int, excelFileName string, pwd string, dataList []read.PropertyStruct) {
    file := HandleFiles(sheetName, index, excelFileName, pwd, dataList)

    sheetIndex := file.HandleSheet(sheetName)

    log.Printf("Handled Sheet Index: %d", sheetIndex)

    file.file.HandlePropertiesColumns()

    file.HandlePropertiesRows(dataList)

    file.SaveExcelFile()
}
  • property 파일들이 저장되어 있는 디렉토리 내의 폴더와 파일 리스트 내에서 property 파일들만 읽어 처리하기
package libraries

// Import 내용들

// 위의 함수들 호출하여 properties 파일 처리 및 액셀 파일 생성
func CreatePropertiesTranslationData(fileName string, pwd string) {
    entries := read.ReadDir(pwd + "/assets/backend")

    log.Printf("[PROPERTIES] Entry Length: %d", len(entries))

    for i, folderData := range entries {
        folderName := folderData.Name()

        var propertyFileList []fs.DirEntry
        var proPwd string

        // 리드미 파일 혹은 예시 파일들은 제외
        if folderName == "README.md" || folderName == "example.properties" {
            continue
        }

        // 디렉토리인지 파일인지 여부 체크하여 분기처리
        if folderData.IsDir() {
            log.Printf("[PROPERTIES] Folder Name: %s", folderName)

            propertyFileList, proPwd = read.ReadFile(pwd + "/assets/common/" + folderName)

            if propertyFileList == nil {
                log.Printf("[PROPERTIES] The File is not readable: %s", proPwd)
                continue
            }
        } else {
            log.Printf("[PROPERTIES] Common File Name: %s", folderName)

            propertyFileList, proPwd = read.ReadFile(pwd + "/assets/common")

            if propertyFileList == nil {
                log.Printf("[PROPERTIES] The File is not readable: %s", proPwd)
                continue
            }

            folderName = "default"
        }

        fileDataList := read.ReadProperties(propertyFileList, proPwd)
        log.Printf("File Data List: %d", len(fileDataList))

        write.OpenSaveExcelWithProperties(folderName, i, fileName, proPwd, fileDataList)
    }
}

정리

  1. 디렉토리 내의 번역 파일 / 하위 디렉토리를 조회
  2. 번역 파일 / 하위 디렉토리 내부의 번역 파일 읽기
  3. 데이터를 파싱하여 원하는 형태로 나눠 구조체 안에서 관리
  4. 액셀 파일 생성

문제해결

  • 디렉토리 내부의 하위 디렉토리를 따로 감지하여 파일 읽는 방법
    • golang에 디렉토리 여부를 체크할 수 있는 기능이 내장되어 있음
  • 함수의 분리
    • 로직을 최소 기능 단위로 분리하여 함수 먼저 작성
    • 함수 Receiver를 이용해 클래스 내부 메서드처럼 활용할 수 있게 작성
  • 에러 발생 시 처리
    • 요구사항 상, 프로그램이 종료 되기보다 에러 발생 부분은 건너뛰고 처리
    • 로그를 확인하여 해당 부분은 매뉴얼하게 체크하는 방식을 선택함
728x90
반응형
728x90
반응형

JSON 파일 읽기

  • json 파일은 /assets/front 디렉토리 아래에 위치한다.
  • 파일은 한국어 번역 파일인 ko.json 과 en.json으로 구성됨
  • json 형식의 번역 파일은 프론트에서 다국어 처리를 위해 사용된다.
func ReadJson(fileName string) ([]byte, error) {
    // 현재 프로젝트 루트 디렉토리
    pwDir, getPwdErr := os.Getwd()

    // 에러 발생 시, 에러 리턴. 에러 발생 파일은 스킵할 수 있게 하기 위하여 리턴함
    if getPwdErr != nil {
        log.Printf("[READ_JSON] Get Pwd Error: %v", getPwdErr)
        return []byte{}, getPwdErr
    }

    file, readErr := os.ReadFile(pwDir + "/assets/front" + fileName)

    if readErr != nil {
        log.Printf("[READ_JSON] Read File Error\nFileName: %s, Error: %v", fileName, getPwdErr)
        return []byte{}, readErr
    }

    return file, nil
}

JSON 데이터 파싱

  • 파일명에 따라 한국어 / 영어로 구분
  • json 파일의 형식은 '{First: {Second: { Third: {"단어": "번역내용"}}}}' 의 형식으로 되어있다.
    • Third 에서 depth가 더 들어가는 케이스도 있음
type JsonRowStruct struct {
    First string
    Second string
    Third string
    Fourth string
}

func ParseJsonData(jsonData []byte) (map[string]interface{}, error) {
    // json 파일 데이터 매핑할 맵 변수 선언 -> 키값 아래에 데이터를 매핑시키는 방식으로 관리
    var jsonDataMap map[string]interface{}

    // json 데이터를 위에서 선언한 Map에 넣어주기
    unMarshalErr := json.Unmarshal(jsonData, &jsonDataMap)

    if unMarshalErr != nil {
        log.Printf("[PARSE_JSON] unmarshal json error: %v", unMarshalErr)
        return map[string]interface{}, unMarshalErr
    }

    return jsonDataMap, nil
}

파싱된 JSON 데이터 평탄화

  • 이러한 Depth 들을 평탄화 시켜줘야 액셀 파일에 넣기 용이함
    • 평탄화 하지 않으면 depth 별로 for 문을 돌려야 하고, depth 길이에 따른 동적 처리가 어려워 졌었음
// 액셀 파일 데이터 구조체
type FileWork struct {
    file        *excelize.File
    FileName    string
    SheetLength int
}

// 이미 한글/영어 데이터가 입력 되었을 때, 코드 값을 기준으로 데이터를 입력할 위치 탐색
func (excelData *FileWork)FindCode(sheetName string, rowIndex int, majorDepth , secondDepth, thirdDepth, fourthDepth string) bool {
    // 각 깊이별로 셀 값을 가져옴
    cellFirst, _ := excelData.file.GetCellValue(sheetName, fmt.Sprintf("A%d", rowIndex))
    cellSecond, _ := excelData.file.GetCellValue(sheetName, fmt.Sprintf("B%d", rowIndex))
    cellThird, _ := excelData.file.GetCellValue(sheetName, fmt.Sprintf("C%d", rowIndex))
    cellFourth, _ := excelData.file.GetCellValue(sheetName, fmt.Sprintf("D%d", rowIndex))

    // 데이터 코드 비교
    if cellFirst == majorDepth 
      && cellSecond == secondDepth 
      && cellThird == thirdDepth 
      && cellFourth == fourthDepth {
          return true
      }

        return false
}


// 데이터 평탄화
func (excelData *FileWork)FlattenJsonData(sheetName string, parsedData map[string]interface{}) {
    switch v := data.(type) {
    case map[string]interface{}:
        for key, value := range v {
            newPrefix := prefix

            if prefix != "" {
                newPrefix += "."
            }

            newPrefix += key

            // map value의 데이터 역시 depth가 있을 수 있기 때문에 평탄화 시킨다
            excelData.flattenKorJSON(sheetName, value)
        }
     // 문자로 들어왔으면 카테고리 구분하여 처리 - 이미 front 시트에 데이터가 들어가 있을 때
    case string:
        depths := strings.Split(prefix, ".")
        firstDepth := ""
        secondDepth := ""
        thirdDepth := ""
        fourthDepth := ""

        if len(depths) > 0 {
            firstDepth = depths[0]
        }
        if len(depths) > 1 {
            secondDepth = depths[1]
        }
        if len(depths) > 2 {
            thirdDepth = depths[2]
        }
        if len(depths) > 3 {
            fourthDepth = strings.Join(depths[3:], ".")
        }

        // 엑셀의 모든 행을 탐색하여 코드 일치 여부 확인
        rows, err := excelData.file.GetRows(sheetName)
        if err != nil {
            log.Printf("GetRows Error: %v", err)
            return
        }

        for rowIndex := range rows {
            // 첫 번째 행은 헤더이므로 건너뜀
            if rowIndex == 0 {
                continue
            }

            // 1번째 row는 칼럼 값들이므로, 2번째 줄 부터 데이터 입력(index+1)
            isCodeMatch := excelData.FindCode(sheetName, rowIndex+1, firstDepth, secondDepth, thirdDepth, fourthDepth)

            if isCodeMatch {
                // 'F' 열에 값 삽입
                excelData.file.SetCellValue(sheetName, fmt.Sprintf("E%d", rowIndex+1), v)
                break // 매칭되는 첫 번째 행에만 값을 삽입하고 루프 종료
            }
        }

    default:
        // 다른 타입은 처리하지 않음
    }
}

액셀 파일 생성 및 데이터 저장

  • 데이터를 처리, 액셀 파일 저장하기 위해 github.com/xuri/excelize/v2 외부 패키지를 사용하였다.
  • 매핑된 데이터를 평탄화하여 저장한다.
# 패키지 설치
go get -u github.com/xuri/excelize/v2

// 번역 엑셀 파일 존재 여부 체크
func (excelData *FileWork)CheckIfFileExist() bool {
    _, err := os.Stat(excelData.fileName)

    return !os.IsNotExist(err)
}

// 파일 저장
func (excelData *FileWork) SaveExcelFile() {
    savingErr := excelData.file.SaveAs(excelData.FileName)
    if savingErr != nil {
        log.Printf("[EXCEL_SAVE] Saving File Error: %v", savingErr)
    } else {
        log.Printf("[EXCEL_SAVE] Saving Success: %s", excelData.file.FileName)
    }
}

// 칼럼 설정
func (excelData *FileWork) SetJsonColumns() {
    excelData.file.SetCellValue(sheetName, "A1", "First")
    excelData.file.SetCellValue(sheetName, "B1", "Second")
    excelData.file.SetCellValue(sheetName, "C1", "Third")
    excelData.file.SetCellValue(sheetName, "D1", "Ko")
    excelData.file.SetCellValue(sheetName, "F1", "En")
}

func (excelData *FileWork)WriteJsonExcelFile(parsedData map[string]interface[}) error {
    // 번역 파일 존재 여부에 따라 액셀 파일 여는 방식이 다르므로 미리 선언
    // 프로그램 실행 시 먼저 파일을 만들고, 항상 openFile 하는 방법으로 작성하였으나 여기서는 두가지 방식을 보여주기 위해 분리해서 보여줌    
    if CheckIfFileExist(excelData.fileName) {
        file, openErr := excelize.OpenFile(excelFilePath)

        if openErr != nil {
            log.Printf("[EXCEL_WRITE] Open Excel File for JSON Error: %v", openErr)
            // 파일 오픈 실패 시, 새로 파일 생성
            excelData.file = excelize.NewFile()
        }

        excelData.file = file
    } else {
        excelData.file = excelize.NewFile()
    }

    sheetIndex, indexErr := excelData.file.GetSheetIndex("front")

    if indexErr != nil {
        log.Printf("[EXCEL_WRITE] Failed to get Sheet Index for JSON error: %v", indexErr)
        return indexErr
    }

    excelData.SetJsonColumns()

    return nil
}
728x90
반응형
728x90
반응형
  • 본 내용은 과거 번역 파일 정리 자동화 프로그램 작업에 대한 업무 일지이다.

시작

  • 회사에서 서비스에 사용되는 다국어 데이터의 검수 및 정리를 위한 액셀 파일 필요
  • 수기로 작업하던 방식에 불만을 갖게 되어, 나도 모르게 자동화 프로그램을 만들어주겠다고 선언해 버렸다
  • 기능 정리를 해 보면 아래와 같다.
    • 번역 파일을 액셀파일로 만들고, 수정 뒤에 다시 액셀파일을 번역 파일 생성해 주는 프로그램 각각을 만들어야 할 것으로 판단하였음
    • 파일 뿐만 아니라, DB의 데이터도 처리해야 해야 했음
  • 복기하는 겸, 그 내용의 일지를 옮겨 적어본다.

액셀 파일 생성 프로그램 - 번역 파일 -> 하나의 액셀 파일

  • 다국어 데이터를 사용하는 서비스는 총 3개
  • 파일들은 백엔드에서 사용하는 파일 / 프론트에서 사용하는 파일 / DB 에서 사용하는 파일 각각은 front / back / DB 폴더 안에 위치
  • 프론트에서 사용하는 다국어는 json 파일로 관리되고, 백엔드에서는 .properties 파일과 DB로 관리됨
    • properties 파일: <단어> = <번역된 단어> 형태로 관리됨
      • 기능에 따라 폴더가 구분되어있음. 이 폴더명을 back_<폴더명> 명으로 사용
      • ko.properties, en.properties로 파일이 나뉘어져 있다.
      • 두 파일은 같은 key를 공유하고 있기 때문에, 하나의 시트에 합쳐서 보여질 수 있게 한다.
      • key, ko, en 으로 칼럼을 잡았다.
    • json 파일: {"key1": {"key2": {"단어": "번역내용"}}} 형태로 관리됨
      • key2 아래에 Depth 가 최대 두개까지 더 들어갈 수 있다.
      • 파일은 한국어 ko.json과 en.json 두가지로 나뉘어지고, 두 파일을 하나의 sheet 에서 보여주게끔 진행해야 함
      • sheet명은 front_json 으로 잡음
      • 따라서 First, Second, Thir, Fourth, Ko, En 을 칼럼으로 잡았다.
    • DB: Sequential 한 id 값이 Primary Key 이고, ko 필드에 한국어 / en 영어 데이터가 저장되어 있음
      • 프로그램 실행 시, DB 에서 데이터 조회
      • 테이블 명을 db_<테이블명> 명으로 잡음
      • id, ko, en 으로 칼럼명을 잡았다.

번역 파일 재생성 - 하나의 액셀 파일 -> 기존 폴더 구조에 맞게 번역 파일로 분리

  • 액셀 파일에 저장 및 수정이 되어 전달 받았을 때, 다시 번역 파일 생성 및 데이터 업데이트/삽입 프로그램
  • json, properties 파일과 DB 내용을 다시 업데이트 해 주기
    • sheet 명의 prefix에 따라, front / back / db 로직으로 분리
    • 다시 생성한 번역 파일들은 dist 폴더 아래에 저장
    • properties 파일 - dist폴더 아래에 prefix(back)으로 디렉토리 생성 후 그 아래에 파일 저장
      • 각각 시트명에 대한 폴더 생성
      • ko / en 칼럼들은 en.properties / ko.properties 파일로 분리 시켜 저장
      • <key칼럼> = <번역 내용> 으로 데이터 생성해서 저장
    • json 파일 - dist 폴더 아래에 prefix(front)으로 디렉터리 생성 후 그 아래에 파일 저장
      • ko 칼럼은 ko.json 파일로, en 칼럼은 en.json 파일로 저장
      • First, Second, Third, Fourth 칼럼들은 각각 json의 키깂으로 사용.
    • DB 데이터 - 해당 데이블에 데이터 업데이트 / 삽입
      • id 값을 PK 로 사용하고 있기 때문에, INSERT INTO ON DUPLICATE KEY 로 쿼리
      • 실 사용 서버에 적용시키기 전 개발 서버의 DB에 적용

설계

  • 언어는 Golang으로 선택. 실행 파일을 빌드해서 넘기기에 용이하며, 여러 OS 에서 실행가능함
  • 번역 파일들은 그 폴더 채로 assets 폴더 안에 옮겨두고 프로그램 실행하면 읽고 각 폴더명에 맞게 sheet 생성 및 데이터 저장
    • 폴더 없이 가장 상위 레벨 디렉토리의 파일은 default 라는 시트명 안에 저장
  • DB 데이터들은 데이터 조회 후, 테이블명을 sheet 명으로 지정하고 id - ko - en 형식으로 데이터 저장
  • 액셀 파일들은 하나의 파일 안에 모든 데이터가 저장되어야 함
  • 처리가 완료되면, 프로그램은 종료되고 실행 파일과 같은 디렉토리에 액샐파일 생성
728x90
반응형
728x90
반응형

취약점 공격

SQL Injection

  • 웹 응용 프로그램에 SQL 삽입하여, 서버 데이터 유출 및 관리자 인증 우회 방법
  • 동적 쿼리에 사용되는 입력 데이터에 예약어 및 특수만자 입력되지 않게 필터링시켜 방지
    • 동적 쿼리: 질의어 코드를 문자열 변수에 넣어 조건에 따라 질의를 동적으로 변경하여 처리

크로스사이트 스크립팅 XSS

  • 웹페이지에 악의적인 스크립트를 삽입하여 방문자들의 정보를 탈취 및 비정상적 기능 수행 유발
  • HTML 태그의 사용을 제한 및 스크립트에 삽입되지 않도록 다른 문자로 치환하여 방지

운영체제 명령어 삽입

  • 외부 입력값을 통해 시스템 명령어의 실행 유도하여 권한 탈취 및 시스템 장애 유발
  • 웹 인터페이스를 통해 시스템 명령어가 전달되지 않도록하고, 외부 입력값을 검증 없이 내부 명령어로 사용하지 않게끔 함

서비스 공격 유형

서비스 공격 (Dos; Denial of Service)

  • 서비스 자원을 고갈시킬 목적으로, 다수의 공격자/시스템에서 대량의 데이터를 한 곳의 서버에 집중적으로 전송

Ping of Death

  • Ping 명령을 전송할 때, 인터넷 프로토콜 허용 범위 이상으로 전송하여 네트워크 마비시키는 공격

스머핑 SMURFING

  • IP 나 ICMPO의 특성을 악용하여, 엄청난 양의 데이터를 한 사이트에 집중적으로 보냄으로써 네트워크를 불능 상태로 만드는 공격

DDoS

  • 여러 곳에 분산된 공격 지점에서 한 곳의 서버에 대해 서비스 거부 공격을 수행하는 것
  • 취약점 가진 호스트들에 공격용 툴을 설치해 에이전트로 만듬

네트워크 침해 관련 용어 정리

  • 스미싱: 문자 메세지를 이용해 정보 빼내는 기법
  • 스니핑: 네트워크의 중간에서 남의 패킷 정보를 도청하는 유형. 수동적 공격에 해당
  • ARP 스푸핑: 자신의 물리적 주소(MAC)을 공격 대상의 것으로 변조해서 공격 대상에 도달해야하는 데이터 패킷을 가로채거나 방해

정보 보안 침해 공격 관련 용어 정리

  • 웜: 네트워크를 통해 자신을 복제하여 시스템 부하를 높여 다운시키는 바이러스의 일종.
    • 분산 서비스 거부 공격, 버퍼 오버플로 공격, 슬래머 등
  • 랜섬웨어: 인터넷 사용자의 컴퓨터에 잠입해 내부 문서나 파일등을 암호화하여 돈 요구
  • 트로이목마: 정상적인 기능을 하는 프로그램으로 위장하여 숨어있다가 부작용 일으킴. 복제 기능 없음

취약점 방어

NULL 포인터 역참조

  • NULL Pointer가 가리키는 메모리의 위치에 값을 저장할 때 발생하는 보안 약점
  • 포인터 이용 전 NULL 값 존재 여부 체크 함으로 방지

스택가드

  • 주소가 저장되는 스택에서 발생하는 보안 약점을 막는 기술
  • 프로그램 복귀 주소와 변수 사이 특정 값을 저장한 후, 그 값이 변경되었을 경우 오버플로우 상태로 판단하여 실행 중단
728x90
반응형

'백엔드 Backend' 카테고리의 다른 글

[테스팅] 어플리케이션 테스팅  (2) 2024.11.01
[결합도/응집도] 결합도와 응집도란  (0) 2024.10.24
악성코드 종류  (0) 2024.10.16
SOLID 원칙  (0) 2024.10.08
비동기 Asynchronous 란?  (0) 2024.10.04
728x90
반응형

어플리케이션 테스트

  • 어플리케이션에 잠재된 결함을 찾아내는 일련의 행위 또는 절차
    • Verification 검증 -> 개발자 입장, 소프트웨어 명세서 만족
    • Validation 확인 -> 사용자 입장, 고객 요구사항 만족

어플리케이션 테스트 원리

  • 완벽한 테스트는 불가능
    • 잠재적 결함을 줄일 수 있지만, 결함이 없다고 증명할 수 없음
  • 파레토 법칙
    • 어플리케이션의 20% 코드에서 전체 결함의 80%가 발견
  • 살충제 패러독스
    • 동일한 테스트 케이스 반복하면 더 이상 결함이 반복되지 않음
  • 테스팅은 정황(Context)에 의존적
    • 정황에 따라 테스트 결과가 달라질 수 있기에, 정황에 따른 테스트 수행
  • 오류-부재의 궤변
    • 결함을 모두 제거해도 사용자 요구사항 만족시키지 못하면 품질이 높지 못함

어플리케이션 테스트 분류

실행 여부 따라

  • 정적 테스트 따라 -> 실행X
    • 워크스루(검토회의) -> 전문가 직접 검토, 절차 따라, 오류 조기 검출
    • 인스펙션 -> 워크스루의 발전, 산출된 결과물 품질 평가 및 개선 방법 제시
  • 동적 테스트 -> 실행 O, 모든 단계
  • 화이트박스 테스트 / 블랙박스 테스트

테스트 기반에 따라

  • 명세 기반 테스트 -> 사용자 요구사항 명세
  • 구조 기반 테스트 -> SW 내부 논리 흐름
  • 경험 기반 테스트 -> 테스터의 경험, 체크리스트

시각에 따라

  • Verification 검증 테스트 -> 개발자 시각, 제품 명세서
  • Validation 확인 테스트 -> 사용자 시각, 사용자 요구사항

목적에 따라

  • Recovery 회복 테스트 -> 실패시키고 올바르게 복구되는가
  • Security 안전 테스트 -> 보호 도구가 볼법 침입으로부터 보호하는가
  • Stress 강도 테스트 -> 과부하 시 정상적으로 실행되는가
  • Performance 성능 테스트 -> 실시간 성능, 전체 효율성 등 응답시간 및 처리량
  • Structure 구조 테스트 -> 내부 논리적 경로, 소스코드 복잡도
  • Regression 회귀 테스트 -> 변경/수정된 코드에 새로운 결함이 없는가. 즉 반복 테스트
  • Parallel 병행 테스트 -> 변경된 SW와 기존 SW 동일한 데이터 입력해서 결과 비교

테스트 기법에 따라

화이트박스 테스트

  • 내부 논리적 경로 및 모듈 안의 내용 볼수 있어서, 내부의 논리적인 모든 경로 테스트
  • 종류
    • 기초경로 검사 -> 대표적. 절차적 설계의 논리적 복잡성 측정
    • 제어구조 검사 -> 조건 검사: 논리적 조건
  • 검증 기준
    • 문장 검증 기준 -> 모든 구문 한 번 이상
    • 분기 검증 기준 -> 모든 조건문의 조건식 결과가 (True, False) 한 번 이상
    • 조건 검증 기준 -> 조건문의 개별 조건식 결과가 (True, False) 한 번 이상
    • 분기/조건 기준 -> 분기 검증 기준, 조건 검증 기준 모두 만족

블랙박스 테스트

  • 각 기능이 완전히 작동되는 것을 입증하는 테스트
  • 종류
    • 동치 분할 검사 Equivalence Partitioning Testing
      • 타당/타당하지 않은 입력 자료 개수가 균등할 때, 입력자료에 맞는 결과가 출력되는지 확인
    • 경계값 분석 Boundary Value Analysis
      • 중간값보다 경계값에서 오류 발생 확률 높음. 경계값을 테스트 케이스로
    • 원인-효과 그래프 검사 Cause-Effect Graphing Test
      • 입력 데이트간 관계와 출력에 영향 미치는 상황 분석 후, 효용성 높은 테스트 케이스 선정
    • 오류 예측 검사 Error Guessing
      • 과거의 경험. 확인자 감각으로 테스트
    • 비교 검사 Comparison Testing
      • 여러 버전에 동일한 테스트 자료 제공, 동일한 결과 출력되는지 테스트
        => 동적 테스트, 명세 기반 테스트, 경험 기반 테스트에 해당

728x90
반응형

'백엔드 Backend' 카테고리의 다른 글

[보안] 보안 공격 종류  (0) 2024.11.04
[결합도/응집도] 결합도와 응집도란  (0) 2024.10.24
악성코드 종류  (0) 2024.10.16
SOLID 원칙  (0) 2024.10.08
비동기 Asynchronous 란?  (0) 2024.10.04
728x90
반응형

인덱스

  • 검색 속도를 높이기 위한 색인 기술
  • JOIN / WHERE 에 사용됨
  • 인덱스 없는 데이터 조회 시
    • 전체 데이터 페이지의 첫 레코드 -> 마지막 레코드까지 모두 조회 == FULL SCAN
    • 때문에 속도가 느림
  • 그러나 INDEX를 많이 설정한다고 조회 속도가 빨라지지 않음
    • INDEX는 테이블 형태로 저장하는 것임
    • 즉 인덱스가 많아지면 데이터베이스 메모리를 많이 잡아 먹음
    • 인덱스로 지정된 칼럼 값이 변동되면, 인덱스 테이블이 갱신되므로 느려짐
    • 때문에 쿼리문 자체가 빨라질 수도 있지만, 전체적 데이터베이스 부하가 증가함
  • SELECT는 빠르지만, UPDATE / INSERT / DELETE 속도는 느림
    • UPDATE / DELETE 시 WHERE을 통해 데이터 조회 자체는 빠름
    • 그러나 데이터 변경 / 삭제 자체는 느림

  • 카디널리티 : 카디널리티가 높으면 인덱스 설정에 좋은 칼럼
    • 카디널리티가 높다 == 한 칼럼이 갖고 있는 값의 중복도가 낮음 (대부분 다른 값을 가짐)
    • 인덱스 통해 불필요한 데이터 대부분을 걸러낼 수 있음
  • 선택도: 선택도가 낮으면 인덱스 설정에 좋음
    • 선택도가 높다 == 한 칼럼이 갖고 있는 값 하나로 여러 row가 찾아진다.
  • 조회 활용도: 조회 활용도가 높으면 인데스 설정에 좋은 칼럼
    • WHERE 문의 대상 칼럼으로 많이 활용되는지
  • 수정 빈도: 수정 빈도가 낮으면 인덱스 설정에 좋은 칼럼
    • 인덱스도 테이블
    • 따라서 지정된 칼럼 값이 인덱스 테이블이 새롭게 갱시되어야 함

용어

  • HEAP: 데이터 저장 시 내부적으로 아무런 순서 없이 저장된 데이터 저장 영역
    CREATE INDEX user_idx ON user_table (user_name);
728x90
반응형
728x90
반응형

UBUNTU에 Go 설치하기

  • 항상 컨테이너로 GO 프로그램을 돌렸지만, 불가피하게 로컬에 GO를 설치해 빌드하고 돌려야 할 상황이 와서 해당 내용을 기록한다.

소스 다운 받아서 설치하기

최신 버전 확인

  • 공식 사이트의 Golang 버전 확인 후 다운받기 (2024.10.21 시점 1.23.2)
  • 공식 사이트

WGET을 이용해 다운로드

  • WGET을 이용해 다운받기
    wget https://go.dev/dl/go1.23.2.linux-amd64.tar.gz

압축 해제

  • 다운 받은 압축 파일 해제
    sudo tar -C /usr/local -xzf go1.23.2.linux-amd64.tar.gz

경로 설정

vi ~/.profile

# 파일 마지막 줄 아래에 내용 추가
export PATH=$PATH:/usr/local/go/bin
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin

변경 사항 적용

source ~/.profile

Go 버전 확인

go version

UBUNTU 피키지 매니저로 다운받기

패키지 인덱스 업데이트

sudo apt update

GO 설치

sudo apt-get install golang-go

Go 버전 확인

go version
728x90
반응형

'백엔드 Backend > Golang' 카테고리의 다른 글

[gRPC] Go 언어로 gRPC 사용해보기  (2) 2024.12.24
[Neo4J] 데이터 핸들링하기  (1) 2024.12.09
[Neo4j] Golang 으로 쿼리 핸들링  (0) 2024.12.06
[Neo4j] Golang으로 Neo4j 연동  (0) 2024.12.05
[GO] Goroutine이란?  (0) 2024.10.02
728x90
반응형

아래는 Jenkins 도입 과정과, 겪었던 시행 착오들에 대한 과거 기록들이다.

  • 배포 과정은 아래와 같다.
    • Master 브랜치에 커밋이 발생함을 감지 (Merge 커밋)
    • Matser 브랜치의 소스 코드를 가져와 Docker 이미지 빌드
    • 빌드된 이미지를 Private Registry에 푸시
    • 배포할 서버에 .env 파일과 docker-compose.yml 파일 전송
    • 배포할 서버에 빌드된 이미지를 pull 하여 컨테이너 구동
  • 이를 위해선 Github 연동과 Credentials 생성이 우선이다.

Github 설정

Github SSH KEY 연동

  • 배포용으로 사용할 키 새성
ssh-keygen -t rsa -f jenkins
  • .pub 키를 복사해서 계정/조직의 ssh 키를 추가해 복사해 넣는다
  • Jenkins Credentials 추가
    • Kind: SSH Username with private key
    • ID 임의 설정
    • username: 깃허브 계정명
    • Private key: 생성된 jenkins 키 내용 전체 복사 붙여넣기
    • 이렇게 설정하니, Host key verification failed. 라고 하며 git fetch 실패 에러가 뜨더라
  • Jenkins Secure 설정
    • 위 에러를 해결하기 위해 아래처럼 했다
    • Git Host Key Verification Configuration 설정 변경
      • Accep First connection 설정

Github Access Token

  • 깃헙 계정 에서 personal access token 생성
    • settings - developer settings - personal access tokens - tokens(classic)
    • Classic Token 생성하기
      • exiration: No expiration
      • repo
      • admin:org
      • admin:repo_hook
    • 생성되는 값 복사해두기

Github WebHook 설정

  • 레포지토리/조직의 설정으로 이동
  • 탭 중 webhook 선택
  • Add webhook 선택
    • url에 "${jenkins-url}/github-webhook/"입력
    • 생성하면, 성공 시 초록색 체크와 함께 완료된다
    • 실패 시, 이미 실패한 요청을 선택하여 redeliver하면 해결되더라

Jenkins 설정

Credential 설정

  • Jenkins 관리로 이동
  • Credentials - Stores scoped to Jenkins의 System 클릭 - Global credentials 클릭 -> Add Credentials 클릭
  • 깃허브 레포지토리 / 조직 접근 키 설정 - 깃헙 웹훅 사용 용
    • Kind를 Secret Text로 설정
    • Secret에 위에서 복사한 Personal Access Token 값 붙여넣기
    • ID: 마음껏 설정 - 나중에 이 값으로 깃헙 연동해야 하므로 알 수 있게끔 설정
    • Description: 설명
  • 개인 계정 - 아이템에서 깃헙 레포지토리 접근 용
    • Kind를 Username with password
    • username: 깃허브 계정 명
    • password: Personal Access Token 값
    • ID: 마음껏 설정
    • Description
  • 서버 - 접속 SSH 키 세팅
    • Kind를 Username and SSH
    • private key에 SSH private key 입력
  • 환경변수 파일 - 배포시 서버에 파일 전송.
    • Kind: Secret File
    • Id: 서비스 별로 분리할 수 있게끔 설정
    • 환경변수 파일 업로드

Github webhook 설정

  • Jenkins 관리로 이동
  • System 선택
    • 하단의 Github 탭의 Github Server 클릭
    • Name에 아무거나 임의로 입력
    • API URL은 건들지 말고
    • Credentials 에 위에서 secret text로 생성한 Credentials 선택
    • Manage Hooks 체크
    • Test connection 선택하여 연동 여부 체크

Jenkins Item 설정

  • 실질적인 파이프라인 설정 내용
  • 새로운 Item 클릭

Jenkinsfile로 사용할 시

  • 소스코드 내에 Jenkinsfile을 위치시켜 이를 사용해서 빌드하는 방법
  • Jenkinsfile에서 사용할 키는 위의 SSH Credential
  • pipeline 선택
    • Github project 체크
      • project url 선택: git@github.com:{브랜치명}
      • Github hook trigger for GITScm polling 선택
      • pipeline 변경
        • Definiton을 Pipeline script from SCM 선택
        • SCM을 Git 으로 변경
        • Repository URL에 깃헙 레포 주소 입력
        • Credentials: 깃허브 SSH Credential 선택
        • Branch Specifier: 연동할 브랜치
  • 이제 master 브랜치로 커밋이 감지될 때 마다 Jenkins가 레포 안의 Jenkinsfile을 감지해서 입력된 순서대로 동작할거다

Jenkins 파일 사용 안할 시

  • freestyle project 체크
  • 소스코드 관리 - Git 체크
  • Repository Url: 깃헙 레포 주소 입력
  • Credentials
  • 깃허브 SSH Credential 선택
    • master: 연동할 브랜치 설정 * 빌드 유발
    • Github hook trigger for GITScm polling 선택 * 저장`
  • 이제 Build Steps에 과정을 입력한다.

Plugins

  • Docker common: 도커 통합 플러그인
  • Docker Pipeline
  • SSH Pipeline steps
  • SSH Server
  • Publish Over SSH

에러 해결 - 1 docker.sock 퍼미션

  • Jenkins 빌드 과정에서 도커 이미지 빌드 내용이 있었다
  • 여기에서 permission denied while trying to connect to the Docker daemon socket 에러 발생
  • 결국 호스트의 var/run/docker.sock을 마운트하고 있기 때문이므로, 이를 수정해주었다.
  • # 새로운 계정 생성 sudo adduser jenkins

권한 부여

sudo usermod -a -G docker jenkins
  • 그럼에도 해결되지가 않았다
  • 누군가는 chmod 666으로 변경하면 된다지만, 이럴 경우 권한이 너무 열려버려서 위험해질 수 있다고 한다.
  • 아무튼 확인 결과 컨테이너 내부의 docker.sock은 root:999 으로 잡혀있었다.
    • root:docker 혹은 docker:1000이 되어야 할텐데
  • 때문에 컨테이너 내부 접근해서 직접 퍼미션 체크를 해 줬다.
sudo docker exec -it -u root jenkins-host /bin/bash
chown jenkins:jenkins /var/run/docker.sock
  • 차후에 퍼미션 문제 없이 마운트 시키고 싶다

에러 해결 - 2 bad crum

curl -v -X GET https://{jenkins_url}/crumbIssuer/api/json --user dong:samquinnWkd1

curl -X POST https://{jenkins_url}/job/manage/credentials/store/system/domain/_/createCredentials/build --user dong:samquinnWkd1 -H 'Jenkins-Crumb: 9e107e40a5f7b6597e4020a4680bdda68b6b04c480aba07311166caa3bf98f9f'
728x90
반응형

'데브옵스 devOps > Jenkins' 카테고리의 다른 글

[JENKINS] JenkinsFile 에서의 빌드 위치 및 빌드  (0) 2024.12.23
[Jenkins] 젠킨스란?  (2) 2024.10.07

+ Recent posts