sar를 활용해서 시스템 모니터링을 해보자!

1. sar(Systecm Activity Reporter 이하 sar) 란?

  • sar 는 리눅스에서 유용하게 쓰는 시스템 모니터링 프로그램입니다.
  • 모니터링 대상이 상당히 넓은 편이며 기본값은 CPU 활동에 대한 통계를 출력합니다.
  • 각종 활동에 대한 통계를 다른프로그램을 이용하여 파일로 저장하고 통계치를 리포팅 하는 기능을 제공합니다.
  • sadc에서 생성한 daily activity 파일을 읽어서 보고서를 작성하기도 하고 시스템의 활동 상황을 수집 할 수도 있습니다.

2. sar 명령어로 모니터링 가능한 항목

  • I/O 전송량
  • 페이징
  • 프로세스 생성 숫자
  • 블락 디바이스 활동
  • 인터럽트
  • 네트워크 통계
  • run 큐 및 시스템 부하 평균
  • 메모리와 스왑 공간 활용 통계
  • 메모리 통계
  • CPU 이용도
  • 특정 프로세스에 대한 CPU 이용도
  • inode, 파일, 기타 커널 테이블에 대한 상태
  • 시스템 스위칭 활동(context switch)
  • 스와핑 통계
  • 특정 프로세스 통계
  • 특정 프로세스의 자식 프로세스 통계
  • TTY 디바이스 활동

3. 설치

sar 툴은 sysstat 패키지를 설치하면 포함되어 있습니다

sysstat 이란?

리눅스 성능 측정 도구 패키지입니다.
해당 패키지에는 아래와 같은 성능 분석 툴을 제공합니다. sar을 사용하기 위해선 sysstat 설치해야 합니다.

sysstat 패키지는 다음을 포함하고 있습니다.

  • sar : cpu, memory, network, diks io 등 지표를 수치화하며 파일로 저장.
  • iostat : disk io에 지표 측정
  • mpstat : cpu 지표 측정
  • tapestat : tape 드라이버의 IO 지표 측정
  • pidstat : 특정 프로세스의 CPU 및 스레드 정보 지표 측정
  • cifsiostat : cifs 파일 시스템 지표 측정

설치 명령어

일반적으로 패키지 매니저를 통해서 쉽게 설치가 가능합니다.

$ sudo apt-get install sysstat

4. 실행

여러가지 sar를 활용한 명령어가 있지만, 간단하게 실시간으로 메모리와 CPU 사용을 확인하는 명령어를 작성해봅니다.

# -u는 CPU 사용율을 확인 합니다.
# 공통적으로 1의 뜻은 1초마다 갱신을 3은 3회를 의미 합니다. 
$ sar -u 1 3
Linux 5.10.63-v8+ (raspberrypi)     2021년 12월 10일     _aarch64_    (4 CPU)

11시 09분 25초     CPU     %user     %nice   %system   %iowait    %steal     %idle
11시 09분 26초     all      2.27      0.00      1.01      0.00      0.00     96.73
11시 09분 27초     all      1.75      0.00      0.75      0.00      0.00     97.49
11시 09분 28초     all      2.24      0.00      0.75      0.00      0.00     97.01
평균값:            all      2.09      0.00      0.83      0.00      0.00     97.08

# -q는 Load Average를 확인 합니다. 실행 큐에 쌓여 있는 프로세스 수, 시스템 상의 프로세스 사이즈, Load Average 등을 참조 할 수 있습니다.
$ sar -q 1 3
Linux 5.10.63-v8+ (raspberrypi)     2021년 12월 10일     _aarch64_    (4 CPU)

11시 10분 07초   runq-sz  plist-sz   ldavg-1   ldavg-5  ldavg-15   blocked
11시 10분 08초         0       697      0.00      0.00      0.00         0
11시 10분 09초         0       697      0.00      0.00      0.00         0
11시 10분 10초         0       697      0.00      0.00      0.00         0
평균값:                0       697      0.00      0.00      0.00         0

# -r은 메모리 사용 현황 확인
$ sar -r 1 3
Linux 5.10.63-v8+ (raspberrypi)     2021년 12월 10일     _aarch64_    (4 CPU)

11시 12분 54초 kbmemfree   kbavail kbmemused  %memused kbbuffers  kbcached  kbcommit   %commit  kbactive   kbinact   kbdirty
11시 12분 55초   1494172   4980124   2479504     31.72    452392   2869548   8294984    104.75   1814580   3887664         8
11시 12분 56초   1493668   4979620   2479484     31.72    452392   2870064   8295500    104.75   1814580   3887660         8
11시 12분 57초   1493668   4979620   2479480     31.72    452392   2870060   8295496    104.75   1814580   3887660         8
평균값:          1493836   4979788   2479489     31.72    452392   2869891   8295327    104.75   1814580   3887661         8

# -W은 스왑 발생상황 확인
$ sar -W 1 3
Linux 5.10.63-v8+ (raspberrypi)     2021년 12월 10일     _aarch64_    (4 CPU)

11시 13분 06초  pswpin/s pswpout/s
11시 13분 07초      0.00      0.00
11시 13분 08초      0.00      0.00
11시 13분 09초      0.00      0.00
평균값:             0.00      0.00

더 많은 옵션은 아래 참고자료를 확인해주세요. :)

참고 자료

Posted by lahuman

댓글을 달아 주세요

얼마전 nosql을 왜 써야 하냐는 질문을 받았습니다.

nosql은 개발을 빠르고 편하기 위해 사용하지, 운영 업무 담당자의 입장에서는 관리 되지 않는 데이터라고 말씀 주셨습니다.

제 생각은 명확하게 nosql과 sql은 구조, 사용법이 다르다 이야기 했지만 잘 전달 되지는 않았네요.

SQL vs NoSQL의 내용을 보면 수정이 거의 없는 대량 데이터는 sql보다는 nosql이 같는 잇점이 크다고 이야기 합니다.
(물론 sql 만으로도 처리가 가능하지만요.)

NoSQL은 언제 가장 좋은가요?

  • 정확한 데이터 요구 사항 또는 데이터 자체가 알려지지 않았거나 변경/확장될 수 있음
  • 높은(읽기) 처리량이 필요하지만 데이터를 자주 변경하지 않습니다(즉, 한 번의 변경으로 인해 항상 수십 개의 문서를 업데이트할 필요가 없음).
  • 데이터베이스를 수평으로 확장해야 합니다(즉, 엄청난 양의 데이터를 저장하고 엄청난 읽기 및 쓰기 처리량을 가짐)

출처 : SQL vs NoSQL

이전 회사 경험으로 방화벽 로그를 mongodb(nosql)에 담았습니다.

대량의 데이터를 민첩하게 적재 하였고 샤딩을 사용해 조회 성능도 꽤 우수했습니다.

로그는 기본적으로 여러 방화벽 장비로 부터 다양한 형식으로 되어 있습니다. 따라서 모든 형식을 알지 못하고 필요한 데이터만 정재해서 저장합니다.
모든 방화벽 장비가 필요한 데이터를 제공하지는 않았습니다. 또한 꽤 많은 데이터를 저장하였습니다.

만약 이 방화벽 데이터를 sql형식으로 저장한다면, 데이터 정형화 과정을 거치며 여러 테이블로 분산하여 저장해야 합니다.
이는 데이터 조회시 조인으로 복잡한 sql로 작성해야 합니다. 또한 성능 역시 문제가 될 수 있습니다.
물론 설계의 묘미로 하나의 테이블로 관리 할 수 있습니다. 다만 그렇게 sql을 사용한다면, sql을 사용하는 의미가 있을까요?

방화벽 로그와 같이 수정이 없고, 특정기간 동안 빠른 조회가 필요하는 대량의 데이터를 쌓는 경우 nosql이 적합하다고 생각합니다.

참고자료

Posted by lahuman

댓글을 달아 주세요

TEAMS 채팅방 생성 및 메시지 발송까지

우선 시작하기에 앞서서 TEAMS의 graph api는 application의 채팅방 생성(Create chat)을 지원하지 않습니다. 이를 해결하기 위해서 진행한 내용을 정리했습니다.

1. 채팅방 생성을 하고, 메시지를 발송하는 역할의 계정을 생성해야 합니다.

계정의 Access token을 이용해서 채팅방을 생성하고 메시지 발송을 하는 teams graph api를 호출합니다.

2. 계정 생성이 완료 되었으면, 채팅 관련 권한을 할당합니다.

권한 할당은 graph-explorer를 통해서 쉽게 할 수 있습니다.

3. 사용자의 Access token을 저장하는 프로그램을 작성합니다.

시작에도 이야기 했듯이 Teams graph api의 Create chat기능은 Delegated를 통해서만 사용이 가능합니다.

| Permission type | Permissions (from least to most privileged) |
|:--------|:-------:|--------:|
| Delegated (work or school account) | Chat.Create, Chat.ReadWrite |
|----
| Delegated (personal Microsoft account) | Not supported. |
|----
| Application | Not supported. |
{: rules="groups"}

따라서 사용자의 Access token을 받아오는 프로그램이 필요합니다.

여기에는 2가지 옵션이 있습니다.

  • app 을 생성해서 등록 후 Access token을 생성하는 방법
  • graph-explorer를 통해서 Access token을 획득하는 방법

기본적으로 사용자의 로그인 행위(위임) 없이는 원하는 Access token 생성이 불가능 합니다.

제 경우는 puppeteergraph-explorer 서비스를 활용해 Access token을 서버에 저장 후 사용했습니다.

Access token은 1시간 동안 유효합니다.

매 50분마다 Access token 재할당 받아 서버에 저장하도록 처리했습니다.

4. 채팅방 생성

채팅방을 생성 할때 가장 중요한 옵션은 그룹 대화방일대일 대화방에 대한 결정입니다.

  • 일대일 대화방은 다른 사용자를 초대하지 못합니다.
  • 그룹 대화방의 경우 다른 사용자를 초대 / 제외가 가능합니다. 다만, graph api 를 이용해서 채팅방을 생성시 이전 대화목록을 보여주는 옵션을 현재까진 지원하지 않습니다.

기본 형식

POST /chats

요청 예제

POST https://graph.microsoft.com/v1.0/chats
Content-Type: application/json

{
  "chatType": "oneOnOne", // 그룹일 경우 'group'로 설정해야합니다.
  "members": [
    {
      "@odata.type": "#microsoft.graph.aadUserConversationMember",
      "roles": ["owner"],
      "user@odata.bind": "https://graph.microsoft.com/v1.0/users('kk.lim@gsretail.com')"
    },
    {
      "@odata.type": "#microsoft.graph.aadUserConversationMember",
      "roles": ["owner"],
      "user@odata.bind": "https://graph.microsoft.com/v1.0/users('82af01c5-f7cc-4a2e-a728-3a5df21afd9d')"
    }
  ]
}

응답 예제

HTTP/1.1 201 Created
Content-Type: application/json

{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#chats/$entity",
    "id": "19:82fe7758-5bb3-4f0d-a43f-e555fd399c6f_8c0a1a67-50ce-4114-bb6c-da9c5dbcf6ca@unq.gbl.spaces",
    "topic": null,
    "createdDateTime": "2020-12-04T23:10:28.51Z",
    "lastUpdatedDateTime": "2020-12-04T23:10:28.51Z",
    "chatType": "oneOnOne"
}

일대일 채팅방은 다시 채팅방을 생성하여도 동일한 id로 응답합니다.

id 값은 메시지를 전송하거나, 다른 사람을 초대 / 제외 시 사용됩니다.

아래 예제 부터는 그룹 채팅방을 생성하고 19:2da4c29f6d7041eca70b638b43d45437@thread.v2을 응답 id로 받았을 경우입니다.

5. 메시지 전송 처리

메세지 전송을 위해서는 아래와 같은 권한이 필요합니다.

| Permission type | Permissions (from least to most privileged) |
|:--------|:-------:|--------:|
| Delegated (work or school account) | ChatMessage.Send, Chat.ReadWrite |
|----
| Delegated (personal Microsoft account) | Not supported. |
|----
| Application | Not supported. |
{: rules="groups"}

기본 형식

POST /chats/{chat-id}/messages

요청 예제

POST https://graph.microsoft.com/v1.0/chats/19:2da4c29f6d7041eca70b638b43d45437@thread.v2/messages
Content-type: application/json

{
  "body": {
     "content": "Hello world",
     "contentType": "text" // 기본 값은 text 이며, "html"으로 설정하면 html 형식으로 전송이 가능합니다.
  }
}

응답 예제

HTTP/1.1 201 Created
Content-type: application/json

{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#chats('19%3A2da4c29f6d7041eca70b638b43d45437%40thread.v2')/messages/$entity",
    "id": "1616991463150",
    "replyToId": null,
    "etag": "1616991463150",
    "messageType": "message",
    "createdDateTime": "2021-03-29T04:17:43.15Z",
    "lastModifiedDateTime": "2021-03-29T04:17:43.15Z",
    "lastEditedDateTime": null,
    "deletedDateTime": null,
    "subject": null,
    "summary": null,
    "chatId": "19:2da4c29f6d7041eca70b638b43d45437@thread.v2",
    "importance": "normal",
    "locale": "en-us",
    "webUrl": null,
    "channelIdentity": null,
    "policyViolation": null,
    "from": {
        "application": null,
        "device": null,
        "conversation": null,
        "user": {
            "id": "8ea0e38b-efb3-4757-924a-5f94061cf8c2",
            "displayName": "Robin Kline",
            "userIdentityType": "aadUser"
        }
    },
    "body": {
        "contentType": "text",
        "content": "Hello World"
    },
    "attachments": [],
    "mentions": [],
    "reactions": []
}

6. 멤버 초대 처리

기본 형식

POST /chats/{chat-id}/members

요청 예제

POST https://graph.microsoft.com/v1.0/chats/19:cf66807577b149cca1b7af0c32eec122@thread.v2/members
content-type: application/json

{
    "@odata.type": "#microsoft.graph.aadUserConversationMember",
    "user@odata.bind": "https://graph.microsoft.com/v1.0/users/8b081ef6-4792-4def-b2c9-c363a1bf41d5",
    "visibleHistoryStartDateTime": "2019-04-18T23:51:43.255Z",
    "roles": ["owner"]
}

응답 예제

HTTP/1.1 201 Created
Location: /chats/19:cf66807577b149cca1b7af0c32eec122@thread.v2/members/MCMjMjQzMmI1N2ItMGFiZC00M2RiLWFhN2ItMTZlYWRkMTE1ZDM0IyMxOTpiZDlkYTQ2MzIzYWY0MjUzOTZkMGZhNjcyMDAyODk4NEB0aHJlYWQudjIjIzQ4YmY5ZDUyLWRjYTctNGE1Zi04Mzk4LTM3Yjk1Y2M3YmQ4Mw==

7. 멤버 제외 처리

멤버 제외의 경우 채팅방의 멤버 id를 조회 하고 해당 id를 DELETE 호출을 해야 합니다.

7.1 멤버 조회

기본 형식

GET /chats/{chat-id}/members
GET /users/{user-id | user-principal-name}/chats/{chat-id}/members

요청 예제

GET https://graph.microsoft.com/v1.0/me/chats/19:8b081ef6-4792-4def-b2c9-c363a1bf41d5_5031bb31-22c0-4f6f-9f73-91d34ab2b32d@unq.gbl.spaces/members

응답 예제

HTTP/1.1 200 OK
Content-type: application/json

{
   "@odata.context":"https://graph.microsoft.com/v1.0/$metadata#users('8b081ef6-4792-4def-b2c9-c363a1bf41d5')/chats('19%3A8b081ef6-4792-4def-b2c9-c363a1bf41d5_5031bb31-22c0-4f6f-9f73-91d34ab2b32d%40unq.gbl.spaces')/members",
   "@odata.count":3,
   "value":[
      {
         "@odata.type":"#microsoft.graph.aadUserConversationMember",
         "id":"8b081ef6-4792-4def-b2c9-c363a1bf41d5",
         "roles":[
            "owner"
         ],
         "displayName":"John Doe",
         "userId":"8b081ef6-4792-4def-b2c9-c363a1bf41d5",
         "email":"kk.lim@gsretail.com",
         "tenantId":"6e5147da-6a35-4275-b3f3-fc069456b6eb",
         "visibleHistoryStartDateTime":"2019-04-18T23:51:43.255Z"
      },
      {
         "@odata.type":"#microsoft.graph.aadUserConversationMember",
         "id":"2de87aaf-844d-4def-9dee-2c317f0be1b3",
         "roles":[
            "owner"
         ],
         "displayName":"Bart Hogan",
         "userId":"2de87aaf-844d-4def-9dee-2c317f0be1b3",
         "email":"abc.def@gsretail.com",
         "tenantId":"6e5147da-6a35-4275-b3f3-fc069456b6eb",
         "visibleHistoryStartDateTime":"0001-01-01T00:00:00Z"
      },
      {
         "@odata.type":"#microsoft.graph.aadUserConversationMember",
         "id":"07ad17ad-ada5-4f1f-a650-7a963886a8a7",
         "roles":[
            "owner"
         ],
         "displayName":"Minna Pham",
         "userId":"07ad17ad-ada5-4f1f-a650-7a963886a8a7",
         "email":"ghi.hkl@gsretail.com",
         "tenantId":"6e5147da-6a35-4275-b3f3-fc069456b6eb",
         "visibleHistoryStartDateTime":"2019-04-18T23:51:43.255Z"
      }
   ]
}

7.2 멤버 제외

채팅창의 멤버정보를 조회 후 원하는 제외를 원하는 멤버의 email정보와 함게 제공되는 id 값으로 삭제를 진행합니다.

기본 형식

DELETE /chats/{chat-id}/members/{membership-id}

요청 예제

DELETE https://graph.microsoft.com/v1.0/chats/19:cf66807577b149cca1b7af0c32eec122@thread.v2/members/MCMjMjQzMmI1N2ItMGFiZC00M2RiLWFhN2ItMTZlYWRkMTE1ZDM0IyMxOTpiZDlkYTQ2MzIzYWY0MjUzOTZkMGZhNjcyMDAyODk4NEB0aHJlYWQudjIjIzQ4YmY5ZDUyLWRjYTctNGE1Zi04Mzk4LTM3Yjk1Y2M3YmQ4Mw==

응담 예제

HTTP/1.1 204 No Content

7. 마치며

현재 Teams graph api는 app이 처리 할수 있는 역할이 매우 제한적입니다.

기존에 workplace에서 사용하던 방식이 필요하여 고민하고 처리한 내용을 정리 했습니다.
teams를 사용하는 분들에게 도움이 되었으면 좋겠네요.

또한 Teams graph api에서 많은 기능을 제공하는 업데이트를 기대하고 있습니다.

참고자료

Posted by lahuman

댓글을 달아 주세요

ssh tunneling으로 port forword

특정 서비스 DBMS에 접근을 해야 하는 일이 있는데, 기본적으로 DBMS에 접근 가능한 서버는 외부에서 접근이 불가능한 상태입니다.

그래서 ssh에서 제공하는 터널링을 이용해서 DBMS에 접근해보려고 합니다.

Mac, Linux 에서는 아래 명령어를 이용하면 쉽게 터널링을 할수 있습니다.

# ssh ID@GateServer -N -L localPort:DBMS:ServicePort
$ ssh ec2-user@1.2.3.4 -N -L 13306:1.2.3.5:3306

-N 옵션은 리모트로 명령어 실행을 하지 않는다는 의미 이며, -L 옵션은 로컬 포트와 GATESERVER 포트를 연결하는 역활을 합니다.

여기서는 GATEServer에서 DBMS와 연결하도록 설정하였습니다.

이렇게 ssh 명령어로 port forward를 사용가능합니다.

참고자료

Posted by lahuman

댓글을 달아 주세요

wsl2의 node기반 프로젝트에서 sqlite3를 사용하기 위해서 설치되어야 할 라이브러리와 설정, 리빌드가 필요합니다.

먼저 sqlite3를 사용하기 위해서는 다음 라이브러리가 설치되어 있어야 합니다.

  • make
  • g++
  • python2 or python3

라이브러리 설치 하기

$ sudo apt install make g++ python3

node에서 python3를 사용하도록 설정합니다.

$ npm config set python python3

node-gyp rebuild

$ npm rebuild node-gyp
$ npm install sqlite3

이렇게 하면, sqlite3가 오류없이 설치되어서 사용할 수 있습니다.

참고자료

Posted by lahuman

댓글을 달아 주세요

Google Cloud 서비스 계정으로 Google API 사용하기

Google Sheet API를 사용하기 위해서 Google cloud console에서 서비스 계정을 만들어 진행하는 과정을 정리합니다.

준비 사항

Google cloud console 에서 프로젝트 & 서비스 계정 생성 처리

  1. Project 생성
  2. LEFT MENU >> APIs & Services 선택
  3. ENABLE APIS AND SERVICES. 클릭
  4. API 항목에서 Google Sheets API 활성화 처리
  5. LEFT MENU >> APIs & Services >> Credentials >> Manage service accounts >> Create Service Accounts 선택
  6. 계정 정보 입력 후 계정 생성 완료(2, 3 단계는 옵션이라 그냥 완료 하면 됩니다.)
  7. 생성된 계정(email) 정보 클릭 >> KEYS 선택
  8. ADD KEY >> Create new key 선택
  9. JSON TYPE 선택 하여 파일 다운로드

첨부된 파일의 형태는 아래와 같습니다.

{
  "type": "service_account",
  "project_id": "keen-clarity-323809",
  "private_key_id": "cd53576f5c47b2b654605c8a7528cabd87bdee93",
  "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQClhKfQF6zmI34T\nuetB4vZullnrXI65fkH1/YusnE/uU1rzmWPNarBqhbxzGeSJf0xedVvSW4cNavkG\nDHu3+inlbbieX9xh++2lNGpYwA5BsXPA3xD1lcaWyc71gk6V6kVtHJMFSocLCYZ/\nDCvGfYEcCiZRsrro1M6AuGy6Nug1qld7utCHJY8OwBp/KLyCmpmhXZE5qim8eul/\nLssM2QizCVxegXS3sH6BCUBZOJVVFXvfNveXc23TWvBQJP85/vaJARjVOJeNJbFQ\nObPOdvEo9RYEfmkrIaaFq/natGP/buBYLdPNnS6+x+6fKRyT5rm1q5LUrpyYKadi\nl40HJFNNAgMBAAECggEAHScTQ5SX8GT59NF/PTiTfQDgT5Ma1FMHnSMOMdXKQkjO\nMS+oI2sSLYDmo0NBPHJ+afiztoZGcDeSKt2SAOJ/9Jfxt3tc3qk3vyxPOtiA6lMt\n9x+S5h5+PtrGp/lOjBsaJVCuGHV6+Zew57mmECzUy7KB80vOnem1gXh6NgYruh/A\ntOATcB7BVk7DEDu7mUAM4BPBWb946WeCUr1L4ZD5VM8YdLAydTCGL39/6TKIDAGl\njDEe+x9IK6DTjm0JHsY9orTDzBLzMCRulOLeAIFzZxpnUnihafk/IQ9+l8SH+rkM\nc/008CMOnUpySLx6wwdCvz+TOhucW1a2RfbGYFUExQKBgQDivirN1jvMzzyQIu8U\nzaNsNPVNIMktLSUNzQEnZsMUGXBUfnAszi3OAHY6NYSEJjYMBFThIvuHqhZCUStV\nNPFTzuHx6mg5Oi4QQC8iW/x9mbeQNBsTiY/nZomgnVS8Kko2SKmINxOf1WkGFcyV\nKk+porKTIkW/m7AEsYLzby07wwKBgQC64BkyQib1sxS1hMVhP3l+A+m7OdbWLDDO\nDRJpxHNZW/Svs1egSF2wNqPZFKJ/LHwAPKxNaBCnfQMjxweDY9VzKBIwXl3boJ7m\nvRulzFsBTwRtYG9QKXYLRhvyZRHFs12XV3KISAy5X+kWG7JeZOj4jeAr0N0Qun6F\n9M+THYMTrwKBgAWTv+HIPzhOcKLq+Q598GMc+lunTst5rMumGz0o3euEpKqvYXr4\np0/F2yKbZmMJvZKWGLBg9+biXCHNdU9nOfhhwlT8+wtNTwy/R2mE8bT1LvqxDWlx\nnMSIVSJKPGdUcvba2rCrCiSIT0kDCEEzBTqa8eGEmkqYPcAfzaTHO0mZAoGADGJD\naTA3ErsuWSUWS26AZ5hsycp4cTL2fQEiwj2Rg09ztJ0G5olFJCNK0lzqs9DH2uAq\nburBh8fiCGHtHojkIUB7jBcE00Qeo53OkjsroLeSzIjCd6Z3uyGHQpXuCpLrVdcm\nsN9NcI9pi9yEAntfcPE99MlfjPc+4TOq+c3P3OMCgYB6YFCFBNpPdYqc8EkQ8OUc\nA2MDWRcCW9oKYvu2zvDLjq9zkznUMcW5PaPql1nIEfNW9LNzn/Bc3nSQ1iHaulGZ\nZjaCKdD6pftkMYC73FWIZXIsvmyC8T7TvRvFppdBHjPcSurOJi6ot74MHKhFudm6\nezS2Wh+8ABQ+ucrWUxpMOw==\n-----END PRIVATE KEY-----\n",
  "client_email": "sheetapi@keen-clarity-323809.iam.gserviceaccount.com",
  "client_id": "114733049967179666126",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/sheetapi%40keen-clarity-323809.iam.gserviceaccount.com"
}

동영상으로 따라 하기

읽거나 쓰려는 sheet에 권한 부여 하기

Google sheet에서 Share 버튼을 누른 이후, 생성한 서비스 계정에 READ/WRITE 권한을 부여 해야됩니다.

sheet 글 읽기

다음 라이브러리를 설치 해야 합니다.

npm install –save googleapis

sheet 읽고 쓰는 예제 코드

import { google, Auth } from 'googleapis';
class SheetApi {
    auth: Auth.GoogleAuth = new google.auth.GoogleAuth({
        keyFile: "credentials.json", //the key file
        //url to spreadsheets API
        scopes: "https://www.googleapis.com/auth/spreadsheets",
    });

    writeSheet() {
        //Auth client Object
        const authClientObject = await this.auth.getClient();
        //Google sheets instance
        const googleSheetsInstance = google.sheets({ version: "v4", auth: authClientObject });
        const spreadsheetId = "sheetId";

        //write data into the google sheets
        await googleSheetsInstance.spreadsheets.values.append({
            auth, //auth object
            spreadsheetId, //spreadsheet id
            range: "Sheet1!A:B", //sheet name and range of cells
            valueInputOption: "USER_ENTERED", // The information will be passed according to what the usere passes in as date, number or text
            resource: {
                values: [["첫번째 A 열 데이터", "1열 B열"], ["두번째 A 열 데이터", "2열 B열"]],
            },
        });
    }

    readSheet() {
        //Auth client Object
        const authClientObject = await this.auth.getClient();
        //Google sheets instance
        const googleSheetsInstance = google.sheets({ version: "v4", auth: authClientObject });
        const spreadsheetId = "sheetId";

        //Read front the spreadsheet
        const { data: { values } } = await googleSheetsInstance.spreadsheets.values.get({
            auth: this.auth, //auth object
            spreadsheetId, // spreadsheet id
            range: "DATA!A:B", //range of cells to read from.
        });

        return values;
    }
}

참고자료

Posted by lahuman

댓글을 달아 주세요

WSL 내에서 웹서버를 띄어서 확인이 안되는 경우 다음과 같이 처리 하여 주세요.

port foward를 위해서는 Netsh를 이용할 예정입니다.

1. net-tools를 WSL 안에 설치 하기

ubuntu 기준으로 다음의 명령어를 이용해서 설치 합니다.

sudo apt install net-tools

powser shell 실행 파일 생성하기

Netsh 설정이된 ps1 의 확장자를 가진 파일을 생성합니다.

다음을 network.ps1 이라는 파일을 생성합니다.

If (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {   
  $arguments = "& '" + $myinvocation.mycommand.definition + "'"
  Start-Process powershell -Verb runAs -ArgumentList $arguments
  Break
}

$remoteport = bash.exe -c "ifconfig eth0 | grep 'inet '"
$found = $remoteport -match '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}';

if ($found) {
  $remoteport = $matches[0];
}
else {
  Write-Output "IP address could not be found";
  exit;
}

$ports = @(3000, 5500, 8080);

Invoke-Expression "netsh interface portproxy reset";

for ($i = 0; $i -lt $ports.length; $i++) {
  $port = $ports[$i];
  Invoke-Expression "netsh interface portproxy add v4tov4 listenport=$port connectport=$port connectaddress=$remoteport";
  Invoke-Expression "netsh advfirewall firewall add rule name=$port dir=in action=allow protocol=TCP localport=$port";
}

Invoke-Expression "netsh interface portproxy show v4tov4";

여기서 중요한 것은 $ports = @(3000, 5500, 8080); 부분입니다.

forward 할 port를 작성합니다.

3. 실행하기

powser shell에서 실행을 하면 다음과 같은 오류 메시지를 받을 수 있습니다.

 $> .\network.ps1
.\network.ps1 : 이 시스템에서 스크립트를 실행할 수 없으므로 C:\DEV\network.ps1 파일을 로드할 수 없습니다. 자세한
내용은 about_Execution_Policies(https://go.microsoft.com/fwlink/?LinkID=135170)를 참조하십시오.
위치 줄:1 문자:1
+ .\network.ps1
+ ~~~~~~~~~~~~~
    + CategoryInfo          : 보안 오류: (:) [], PSSecurityException
    + FullyQualifiedErrorId : UnauthorizedAccess

해결을 위해서 powser shell을 관리자 모드로 실행하고, Set-ExecutionPolicy -ExecutionPolicy RemoteSigned 명령어로 실행 규칙을 변경합니다.

$> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned

실행 규칙 변경
실행 정책은 신뢰하지 않는 스크립트로부터 사용자를 보호합니다. 실행 정책을 변경하면 about_Execution_Policies 도움말
항목(https://go.microsoft.com/fwlink/?LinkID=135170)에 설명된 보안 위험에 노출될 수 있습니다. 실행 정책을
변경하시겠습니까?
[Y] 예(Y)  [A] 모두 예(A)  [N] 아니요(N)  [L] 모두 아니요(L)  [S] 일시 중단(S)  [?] 도움말 (기본값은 "N"): Y

이후 다시 실행하면 다음과 같이 정상 동작을 확인 할 수 있습니다.

$> .\network.ps1


확인됨


확인됨


확인됨


ipv4 수신 대기:             ipv4에 연결:

주소            포트        주소            포트
--------------- ----------  --------------- ----------
*               5500        192.168.254.148 5500
*               3000        192.168.254.148 3000
*               8080        192.168.254.148 8080

4. 테스트

WSL에서 5500 포트로 웹을 띄우과 브라우져에서 접속하여 확인하면 잘 동작 하는 것을 확인 할 수 있습니다.

참고 자료

Posted by lahuman

댓글을 달아 주세요

자바라 쓰고 Springframework를 공부한다.

Spring의 주요 3가지 컨셉은 아래와 같습니다.

1) IOC : 제어의 역전 / 의존성 주입
2) AOP : 관심의 분리
3) PSA : 일관성 있는 추상화

그럼 실제로 어떻게 사용될까?

IOC : 제어의 역전 / 의존성 주입 사용 예제

Bean은 IoC 컨테이너 안에 등록된 객체들을 의미 합니다.
모든 클래스의 객체가 Bean으로 등록되지 않습니다. @Repository, @Component, @Service, @Bean 등의 어노테이션을 통해서 Bean으로 등록 가능합니다.

사용 예로는 @Autowired 어노테이션을 이용해서 Bean을 주입할 수 있습니다.
Bean 주입이 주는 이점은, 객체의 관리를 스프링 컨테이너가 하기에 개발자가 언제 빈을 생성하고 소멸시킬지 신경쓰지 않아도 됩니다.

AOP : 관심의 분리 예제

가장 큰 예로 @Transctional을 이야기 할 수 있습니다.
connection에 대하여 rollback, commit등을 신경쓰지 않고 처리 하게 됩니다.
결국 트렌젝션 처리는 위임하고 구현 기능에 집중하는 코드를 작성하면 됩니다.

PSA : 일관성 있는 추상화

Service Abstraction으로 제공되는 기술을 다른 기술 스택으로 간편하게 바꿀 수 있는 확장성을 갖고 있는 것이 Portable Service Abstraction. 줄여서 PSA라고 합니다.
예로는 서블릿을 직접 사용하는 것이 아니라, Controller의 @GetMapping이나 @PostMapping을 통해 특정 url로 요청이 들어왔을 때, 해당 블록이 요청을 처리하도록 구현 되어 있습니다.
이렇게 추상화 계층을 사용해 어떤 기술을 내부에 숨기고 개발자에게 편의성을 제공하는 것을 Service Abstraction이라고 합니다.

스프링은 MVC라는 추상화 기법을 사용. Spring Web MVC를 사용하면 서블릿을 직접 구현할 필요가 없습니다.

참고 자료

Posted by lahuman

댓글을 달아 주세요

couchDB의 변경내역이 발생할 경우 kafka로 해당 데이터를 전송해야 하는 연계성 요청이 들어왔습니다.

여러 가지 방안을 고민하였으나, 확장성과 유연성을 위해서 logstash를 선택하였습니다.

실제로 가능한지 테스트 하기 위해서 개발 환경을 docker 기반으로 구성하여 테스트 했습니다.

시작하기 전에

docker 간의 통신을 위해서 docker network를 추가 합니다.

myHome 이름으로 네트워크를 생성 합니다.

$ docker network create myHome

자세한 docker network 설명은 Docker 네트워크 사용법을 참조하셔요.

couchdb 설정

couchdb는 docker에서 bitnami 에서 제공하는 이미지로 생성하였습니다.

다음 명령어로 image를 다운받고 실행합니다.

$ docker run --network myHome  -p 5984:5984 --name  couchdb bitnami/couchdb:latest

추후 테스트를 위해서 5984 port를 연결합니다.

docker가 온전하게 기동되었다면, http://localhost:5984 에 접근하면 아래와 같은 내용을 확인 할 수 있습니다.

{
  "couchdb": "Welcome",
  "version": "3.1.1",
  "git_sha": "ce596c65d",
  "uuid": "b7449ad8eda14515e4e0782a84c41f5d",
  "features": [
    "access-ready",
    "partitioned",
    "pluggable-storage-engines",
    "reshard",
    "scheduler"
  ],
  "vendor": {
    "name": "The Apache Software Foundation"
  }
}

kafka 설정

kafka의 경우 기본적으로 zookeeper가 필요 하게 됩니다.
이를 쉽게 처리 하기 위해서 docker-compose를 이용합니다.

docker-compose.yml 파일을 생성합니다.

version: "2"

networks:
  default:
    external: true
    name: myHome

services:
  zookeeper:
    image: docker.io/bitnami/zookeeper:3
    ports:
      - "2181:2181"
    volumes:
      - "zookeeper_data:/bitnami"
    environment:
      - ALLOW_ANONYMOUS_LOGIN=yes
  kafka:
    image: docker.io/bitnami/kafka:2
    ports:
      - "9092:9092"
    volumes:
      - "kafka_data:/bitnami"
    environment:
      - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181
      - ALLOW_PLAINTEXT_LISTENER=yes
    depends_on:
      - zookeeper

volumes:
  zookeeper_data:
    driver: local
  kafka_data:
    driver: local

해당 파일의 생성이 완료 되었다면, 다음 명령어로 기동 합니다.

$ docker-compose up

기동이 문제 없이 되었다면 아래와 같은 로그가 확인 됩니다.

kafka_1      | [2021-05-17 03:39:13,937] INFO [GroupMetadataManager brokerId=1001] Finished loading offsets and group metadata from __consumer_offsets-48 in 103 milliseconds, of which 102 milliseconds was spent in the scheduler. (kafka.coordinator.group.GroupMetadataManager)

logstash 설정

먼저 pipline 디렉토리를 생성한 후 logstash.conf 파일을 생성합니다.

$ mkdir pipline
$ vi logstash.conf
# logstash.conf 파일 내용
input {
  couchdb_changes {
    id => "my_plugin_id"
    host => "couchdb"
    username => "admin"
    password => "couchdb"
    port => "5984"
    db => "test"
  }
}

output {
    stdout { codec => "rubydebug" }

     kafka {
       codec => "json"
       topic_id => "test_topic"
       bootstrap_servers => "logstash_kafka_1:9092"
     }
}

주요 정보는 input과 output에 대한 정의 이며, 이후 db 종류가 많이 생기면, input에 추가 하면 됩니다.
kafka 나, couchdb의 host는 docker ps 정보에서 이름을 활용하면 됩니다.

$ docker ps
CONTAINER ID   IMAGE                                            COMMAND                  CREATED          STATUS          PORTS                                                                     NAMES
03bb311bf431   docker.elastic.co/logstash/logstash-oss:7.12.1   "/usr/local/bin/dock…"   50 seconds ago   Up 48 seconds   5044/tcp, 9600/tcp                                                        compassionate_yonath
28a0315d2422   bitnami/kafka:2                                  "/opt/bitnami/script…"   4 days ago       Up 4 minutes    0.0.0.0:9092->9092/tcp, :::9092->9092/tcp                                 logstash_kafka_1
fa1b5e62784b   bitnami/zookeeper:3                              "/opt/bitnami/script…"   4 days ago       Up 4 minutes    2888/tcp, 3888/tcp, 0.0.0.0:2181->2181/tcp, :::2181->2181/tcp, 8080/tcp   logstash_zookeeper_1
786c603b9ae2   bitnami/couchdb:latest                           "/opt/bitnami/script…"   4 days ago       Up 3 hours      4369/tcp, 9100/tcp, 0.0.0.0:5984->5984/tcp, :::5984->5984/tcp             couchdb

준비가 다 되었다면, pipeline 디렉토리 주소를 알맞게 설정후 다음 명령어로 docker 를 기동 합니다.

$ docker run --rm -it -v /Users/admin/DEV/test/logstash/pipeline/:/usr/share/logstash/pipeline/ --network myHome  docker.elastic.co/logstash/logstash-oss:7.12.1

테스트

couchdb의 utils를 이용해서 데이터를 적제 할 수 있습니다.

접근 주소 : http://localhost:5984/_utils/

해당 주소에 접근하여 test database를 생성하고 documents를 생성합니다.

데이터를 생성 하였으면, kafka에 제대로 적재 되었는지 확인해봅니다.

docker exec 명령어를 이용해서 kafka 내부로 접근합니다.

$ docker exec -it  logstash_kafka_1 /bin/bash

첫번째로 topic의 목록을 조회 해봅니다.

$ kafka-topics.sh --list --bootstrap-server localhost:9092
__consumer_offsets
mytopic
test_topic

logstash를 이용하여 생성한 test_topic 이 확인되었다면, 데이터도 console로 확인해봅니다.

$ kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test_topic --from-beginning
{"@timestamp":"2021-05-12T09:22:28.111Z","doc_as_upsert":true,"doc":{"test":"test"},"@version":"1"}
{"@timestamp":"2021-05-17T00:35:33.135Z","doc_as_upsert":true,"doc":{"helo":"kafka"},"@version":"1"}

이로서 연동 테스트가 완료 되었습니다.

참고 자료

Posted by lahuman

댓글을 달아 주세요

프로젝트 내에 여러 profile이 있을 경우 Mavne 빌드시 하나를 선택해야 합니다.

Maven 프로젝트의 Root 디렉토리에서 다음 명령어로 packaging 을 하면 TEST 코드에 대하여 profile을 적용하게 됩니다.

# dev profile을 사용하여 packaging 진행
./mvnw clean package -Dspring.profiles.active=dev

이후 결과 jar 파일을 실행시 profile을 설정하는 것도 동일 합니다.

# dev profile을 사용하여 packaging 진행
java -jar -Dspring.profiles.active=dev result.jar 
Posted by lahuman

댓글을 달아 주세요