GCP와 GKE 기술용어 정리

PUB/SUB

  • GPC용 카프카(Kafka)라고 보면 됩니다. MS(MicroServices)기반의 서비스 플랫폼 환경에서 운영메시지 및 데이터를 활용하기 위해서는 Kafka와 같은 구독기반 시스템이 필요할 때가 있습니다.
  • 제가 구축했던 서비스에서 사용된 데이터는 2가지 특징이 있었는데요.
    • 첫째 대용량 데이터가 있었어요. AI 모델링을 위한 학습데이터 였죠. 양이 많을 수 밖에 없습니다.
    • 둘째 다방면에서 사용되었습니다. 음성데이터를 텍스트 데이터로 그대로 옮긴 후에 단어에 특정 라벨링을 해서 학습서버에 보내기도하지만, 그 라벨링이 얼마나 사용되었는지 카운팅해주는 통계서버에도 데이터를 보내야 하는 경우가 있었죠.

Cloud Speech-to-text

  • 음성을 텍스트로 변환해주는 서비스 입니다.
  • 취급하는 음성데이터의 단위가 짧은(15초단위로 과금) 방식과, 긴 파일을 한꺼번에 변환해주는 방식이 있습니다.
  • 부가기능중에 화자분리가 있는데, 한국어는 지원을 안해주는 문제가 있었어요.
  • 제가 취급했던 음성 데이터는 채널이 분리되서 녹음되는 케이스라 굳이 화자분리를 할 필요는 없었지만, 긴 파일을 한꺼번에 변환해주는 방식보다는 짧은 파일을 변환해주는 방식이 비용이 덜 나가더라구요. 그래서 채널별로 15초 단위로 잘라서 파일을 보내는 루틴을 개발해야 했습니다.

Dialogflow

  • 대화형 인터페이스 서비스 입니다. 챗봇 같은거라고 생각하면 편하지만, 조금더 확장성이 있습니다.
  • 어떻게 보면 챗봇 + 설치형 웹서비스라고 보는게 맞을수도 있겠네요.
  • 이 서비스를 이용해서 모호한 표현이나 잘못된 표현이 들어간 주소를 말해도 정확한 우편번호를 찾아주는 서비스를 만들어 보려고 했는데 생각보다 쉽지 않았던 기억이 있습니다.
    • 예를들어 “서울 중구 세종대로 110”으로 말해야 할 것을 “서울”을 생략하고 말한다거나, “세종대로”를 “세종로”로 한다거나 했을 때도 정확한 위치를 파악해서 우편번호를 찾아주는 서비스 말이죠.
    • 말로 표현할 때는 저렇게 생략되거나 중간중간 잘못된 표현을 사용할 확률이 높기 때문이죠, 그런데 구현이 쉽지 않더라구요.

Cloud DNS

  • Compute Engine이나 Kubernetes Engine에 설치된 가상머신이나 서비스IP를 연결할 수 있습니다. 일반적인 웹호스팅 업체에서 제공하는 기능에 추가적으로 G메일이나 G-Suite같은 기능도 연결해서 사용할 수 있습니다.
  • HTTPS를 이용할 때 인증서 문제가 있는데요, 구글에서 관리해주는 방식이 있고 직접 만든 인증서를 사용하는 방식이 있습니다. 저 같은 경우엔 Lets Encrypts를 이용해서 3개월에 한번씩 갱신해주는 인증서도 써봤고, 돈 주고 산 인증서도 써봤는데 이럴때 후자의 방식을 사용합니다.

Cloud Storage

  • AWS의 S3에 대응되는 서비스라고 보시면 됩니다.
  • 써드파티 사용자권한에 따라서 접근권한을 달리하는 서비스를 구상중이었는데, 퇴사하면서 개발하지 못한 경험이 있네요.

Persistent Disk

  • Compute Engine이나 Kubernetes Engine에서 사용하는 가상하드디스크라고 생각하면 됩니다.
  • SSD를 이용하는게 있고 일반 HDD를 이용하는게 있는데 당연히 SSD 이용하면 비싸겠죠.
  • 백업과 관련하여 신경쓸 부분들이 몇가지 있습니다. 수백기가 정도의 데이터도 상당히 빠르게 백업합니다만, 그 순간에 살짝 느려지는 현상이 있더군요. 그래서 백업을 주로 서비스타임이 아닌 새벽시간에 돌아가도록 해놓았지만, 서비스 업그레이드를 하거나 뭔가 이슈가 발생할 수 있는 상황에서 민감하게 백업을 할 수 있어야 할 것 입니다. 복원하는 과정도 마찬가지죠. 이럴때 분초를 다투는 사투가 벌어질 수 있기 때문에, 미리미리 연습해두는 것도 좋습니다.

Cloud IAM

  • 계정별로 각종 GCP 서비스에 어떻게 접근하게 할 것인지 제어합니다.
  • 예를들어 Cloud Storage에서는 업로드 권한을 주지만 Compute Engine Console에는 접근을 못하게 한다던가 하는 행위를 정의할 수 있는거죠.

Compute Engine

  • 가상서버라고 생각하면 됩니다. 사용하는 환경(MEMORY, CPU, GPU, TPU)에 따라서 가격이 천차만별입니다. 약정하면 더 저렴하게 쓸 수 있구요.

Kubernetes Engeine

  • 위에서 소개한 가상서버 여러개를 묶어서 클러스터를 만듭니다. 그리고 그것들의 자원을 논리적으로 분할하여 여러개의 MS를 넣다/뺐다 하면서 사용합니다.
  • 이 서비스를 사용하기 위해서는 다양한 세부기능과 이슈들을 경험해보는 것이 좋습니다. 그래야 실전에서 효과적으로 사용할 수 있기 때문이죠. 키워드로 정리해보면 다음과 같습니다.
    • Pod를 구성하는 방법 : 스케일 아웃 전략 정의하기, 서비스 정상구동확인 및 재부팅 방식 정의하기, 가상화 이미지 연동시키기(like Docker Image)
    • Persistent Disk를 연결하기
    • 외부와의 연결 : Pod 하나에 사설 IP를 바로 연결하는 방법도 있고, ENVOY나 NGINX등을 이용해서 로드밸런싱 하는 방법도 있습니다.

Cloud Function

  • 서버리스 컴퓨팅 입니다. GCP의 람다 같은거라고 생각하면 됩니다.
  • 제가 사용해본 케이스는 아래와 같습니다.
    • VueJS로 만든 웹프런트 저작도구가 있었습니다. 여기서 데이터가 만들어지면 Go언어 기번의 백엔드 서버로 넘어가는데요, 프런트에서 발생한 오류를 수집하고자 할 때 Cloud Function을 이용했습니다.
    • 처음에는 자체적으로 Fluentd 백엔드 서버를 구성해서 거기로 로그를 수집하려고 했지만, 시간을 아껴야만 했거든요.
    • 그래서 Cloud Function에서 데이터를 받아서 바로 Cloud Logging 서비스로 저장하는 간단한 파이썬 코드를 짜서 로그를 수집했었습니다.

동영상의 특정 영역안에서 움직이는 물체가 있는 구간찾기

최근에 출시되는 CCTV 관제 프로그램에는 움직임을 감지해서, 해당 구간만 조회할 수 있는 기능이 있습니다. 그런데 구형 CCTV 시스템이거나, 원본 동영상 파일만 가지고 있을 때 이런 기능을 구현하려면 별도의 프로그램이 있어야 합니다. 게다가 특정 영역안에서만 움직임을 필터링하려면 몇 가지 조건을 더 추가해 주어야 하지요. 오늘은 이러한 프로그램의 핵심기술이 될 수 있는 루틴을 공유해 보고자 합니다.

def make_frames_and_trace(self, filename):
    # 비디오 캡쳐할 파일을 지정하고 각종 변수 가져오기
    cpt = cv2.VideoCapture(filename)
    frame_count = int(cpt.get(cv2.CAP_PROP_FRAME_COUNT))
    fps = cpt.get(cv2.CAP_PROP_FPS)
    f_width = cpt.get(cv2.CAP_PROP_FRAME_WIDTH)
    f_height = cpt.get(cv2.CAP_PROP_FRAME_HEIGHT)

    # 첫번째 프레임 이미지 얻기
    cpt.set(cv2.CAP_PROP_POS_FRAMES, 0)
    _, frame1 = cpt.read()
    on_blank = True
    prev_second = 0.0

    # 1초 단위로 움직임을 체크하려고 합니다
    fps_i = int(fps)

    has_track = None

    for i in range(1, frame_count - 1, fps_i):
        if i + fps < frame_count:
            # 비교할 프레임 이미지 얻기
            cpt.set(cv2.CAP_PROP_POS_FRAMES, i+fps_i)
            _, frame2 = cpt.read()
            preview = frame1
            dst1 = cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY)
            dst2 = cv2.cvtColor(frame2, cv2.COLOR_BGR2GRAY)
            diff = cv2.absdiff(dst1, dst2)
            ret_thr, thr = cv2.threshold(
                diff, 30, 255, cv2.THRESH_BINARY)
            dilate = cv2.dilate(thr, self.kernel)
            contours, hierarchy = cv2.findContours(
                dilate, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
            has_it = False
            for ct in range(len(contours)):
                # 내가 지정한 영역 rect1과 윤곽선추출된 최소영역인 rect2가 겹치는지를 점검합니다
                area_size = cv2.contourArea(contours[ct])
                min_rect = cv2.minAreaRect(contours[ct])
                rect1 = [self.rect.left(), self.rect.top(),
                         self.rect.right(), self.rect.bottom()]
                rect2 = [min_rect[0][0] / f_width, min_rect[0][1] / f_height,
                         (min_rect[0][0] + min_rect[1][0]) / f_width,
                         (min_rect[0][1] + min_rect[1][1]) / f_height]
                if area_size > 100 and rect2[2] - rect2[0] > 0.01 and rect2[3] - rect2[1] > 0.01 and self.check_overlap(rect1, rect2):
                    x,y,w,h = cv2.boundingRect(contours[ct])
                    # 미리보기 화면 그리기
                    cv2.rectangle(preview, (x,y), (x+w,y+h), (255, 0, 0), 5)
                    has_it = True

            current_second = int((i-1) / fps)

            # 변화가 감지되었나?
            if has_it:
                # 기존에 공백구간이었나?
                if on_blank:
                    on_blank = False
                    prev_second = current_second
                    has_track = TrackItem(filename, current_second, current_second)
                else:
                    prev_second = current_second
                    if has_track is not None:
                        has_track.end = current_second
            else:
                # 10초 이상 감지되지 않았는가? 10초 이상 변화가 없으면 새로운 구간을 만들어도 됩니다
                if current_second - prev_second > 10:
                    on_blank = True
                    if has_track is not None:
                        # 감지된 구간의 길이가 3초 이상일 때만 추가
                        if has_track.end - has_track.start >= 3:
                            self.trackAdded.emit(has_track)
                        has_track = None
            # 감지를 원하는 영역을 미리보기에 그려주기
            cv2.rectangle(preview,
                          (int(self.rect.left() * f_width), int(self.rect.top() * f_height)),
                          (int(self.rect.right() * f_width), int(self.rect.bottom() * f_height)),
                          (0,255,0), 5)
            frame1 = frame2
    if has_track is not None:
        self.trackAdded.emit(has_track)

# arr1과 arr2 구간이 겹치는지 체크
def check_overlap(self, arr1, arr2):
    if arr1[0] == arr1[2] or arr1[1] == arr1[3] or arr2[0] == arr2[2] or arr2[1] == arr2[3]:
        return False
    if arr1[0] > arr2[2] or arr2[0] > arr1[2]:
        return False
    if arr1[1] > arr2[3] or arr2[1] > arr1[3]:
        return False
    return True

그리하여 아래와 같은 응용프로그램을 만들어 볼 수 있었는데요, 테스트가 완료되어서 최소한의 안정화 버전이 갖춰지는대로 github에 공개하고 여기에도 공유해 보겠습니다. 이미지1