Seize the day

SEARCH RESAULT : 글 검색 결과 - 전체 글 (총 466개)

POST : Flutter study

첫 플러터 앱 출시하다.

Flutter 로 만든 앱을 출시했다. https://play.google.com/store/apps/details?id=com.mdiwebma.good_timer

 

Pomodoro Flow - Google Play 앱

간단한 Pomodoro 앱

play.google.com

 

Flutter를 4년만에 다시 시작한 것 치고는  별 문제없이 UX, 개발, 테스트를 동시에 진행하며 개발을 완료했다.  2주 동안 개발하는 내내 몰입하는 즐거움을 느꼈다. 개발에 특별히 엄청난 기술이 들어가지는 않았다. Realm과 Table calendar를 써 본 정도다.  선언형 UI 개발 방식이 명령형 방식보다 개발하기 더 좋은 느낌이다. 느낌이지 확실한 결론은 아니다.  개발 속도도 빠른 것 같고, 적어도 버그의 개수는 줄어들것 같다. 소스코드를 저장만 해도 동작중인 앱에 반영이 되는 것은 큰 장점이다. 그리고 역시나 네이티브를 잘 알고 있는 것이 중요한 것 같다. 플러그인 없이는 괜찮은 기능을 만들기 어렵다. 

top

posted at

2023. 12. 13. 23:01


POST : Flutter study

Flutter 공부 다시 해 볼련다..

22년 6월 부터 사실상 놀고 있다.  이 블로그의 글도 거의 업데이트 하지 않고 있다. 업데이트 하지 않는다는 것은 개발도 하지 않고 있다는 뜻이다. 내가 퇴사한 이유도 근본 원인은 번아웃인데 이게 참 무섭다. 아무것도 하지 않고 몇 달을 지낸적도 있다. 나는 개발이 하고 싶어질 때 까지 계속 기다렸다. 23년도가 2달 남은 최근에서야 잠들기 전에 머리속에서 뭔가가 계속 멤돈다. 플러터를 다시 해야겠다는 생각과 몇 년간 독푸딩하고 있는 앱을 결국에는 출시를 해야한다는 생각이다. 두 가지는 확고한 결심이다. 

완벽주의 성향이 있는 나로써는 뭔가가 충분히 준비되지 않으면 시작하지 못하는 문제가 있다. 안 좋은 습관이다. Flutter 공부도 시작도 전에 계속 뭔가 그럴듯한 앱을 만들 생각부터 했다. 공부도 되고 또 나와 다른 사람에게 유용한 제품도 되는 것을 찾다보니 뭘 만들지 결정하지 못하고 그래서 시작하지 못하는 것 같다.  그러다 오늘 그냥 시작했다. 계산기나 메모장, 체크리스트, 알람 앱 등을 생각하다가 결국에는 뽀모도로라는 것을 만들기로 했다.  내가 매일 쓸 것 같고, 요구사항이 간단하고, 서버가 필요없고, 글로벌로 런칭할 수 있기 때문에 이게 적당해 보인다. 

오늘 3시간 동안 가칭 "내가 만든 뽀모도로"라는 앱을 만들었고 가장 최소한의 기능만 구현하여 폰에 설치하며 독푸딩해봤다. 오랜만이라 오피셜 문서 위주로 다시 들여다 봐야했다.  릴리스 모드 apk가 18메가라니.. 이건 어쩔수 없나보다. 

요구사항
- 시작버튼을 누르면 25분 타이머가 시작된다. 시간은 붉은 색으로 표시된다. 
- 25분이 지나면 소리가 나고 5분 타이머가 시작된다.  시간표시는 검정색으로 바뀐다.
- 5분이 지나면 또 다시 다른 소리가 나고, 다시 25분 타이머가 시작된다. 
- 타이머가 동작중에는 화면이 꺼지지 않는다. 
- 종료버튼을 누르면 대기 상태가 되고, 대기시간이 지나면 화면도 꺼진다.
이 요구사항이 기본 생성 코드에서 40줄 정도 추가하거나 수정하니 잘 동작했다.

wav파일 재생을 위해서 audioplayers  플러그인을 사용했고, 화면 잠기지 않도록 wakelock 플러그인을 사용했다. 

TODO
- 화면이 꺼지면 타이머 멈추는 문제
- 뒤로가기시 종료 확인 뜨게

소스코드
- https://github.com/dajkim76/good_timer

11/24일 업데이트

- only dark theme
- make full screen
- disable back key(WillPopScope),
- add close button to appBar
- apply app name, app icon


- https://github.com/dajkim76/good_timer/commit/a8a99747f2b74a313927c35e29d81822b3c9813d

top

posted at

2023. 11. 23. 03:52


POST : Android Dev Study

안드로이드 다국어 문자열 관리 툴 개선 by Dart lang

https://dajkim76.tistory.com/481

 

안드로이드 다국어 번역툴 #4

앱 다국어 버전을 빨리 만들어야해서 다국어툴 개발을 서두르고있다. 지난번 values/strings.xml에서 xlsx 파일을 생성하는 자바스크립트를 일부 개선했다. StringsToXlsx.js 1. 개선의 요지는 & < > ' " \n 문

dajkim76.tistory.com

이전에 구글 독스에서 언어를 관리하고 편집하고,  툴을 이용해서 xlsx를 내려받아서 안드로이드의 res/values/strings.xml 파일을 자동으로 생성하는 툴(자바 스크립트)을 개발했었다.  이렇게 하는 이유는 언어 번역과 개발 적용을 분리하기 위해서이고, 번역자는 안드로이드 고유의 포맷 문자열 처리등의 개발 지식이 없더라도 쉽게 수정할 수 있는 방법이 필요했기 때문이다.  때문에 문자열에 ' " ${str} ${num} 과 사용자 친화적인 문자열로 입력하면 되고, 툴은 최종적으로 \' \" %s %d 와 같은 문자열로 치환하여 적용한다. 

이게 자바스크립트로 구현했는데 맥 미니에서 동작이 잘 안 되고 (오류를 수정할 수는 있겠지만 귀찮다. node버전과 관련이 있는듯.) 옛날에 Go 언어로 간단한 거 만든적 있고, 파이선도 해 봤지만 문법이 기억나지 않는다. 새로 뭔가를 배워야 한다면 dart 언어로 해 보면 어떨까 싶어서 새로 구현했다. Flutter도 어차피 공부해야해서 언어에 익숙해지는데 도움이 될 듯.

https://dart.dev/tutorials/server/cmdline

 

Write command-line apps

Basics for command-line apps.

dart.dev

프로젝트 생성

dart create lt_lang_utils

 

필요한 라이브러리 추가 xml, excel
excel 최신버전은 xml 최신 버전과 충돌이 있기 때문에 xml의 버전을 5.4.1 로 다운그레이드한다. 

cd lt_lang_utils
dart pub add xml
dart pub add excel

 

스크린 샷 사용자가 ar-strings.xml 파일을 번역해 왔는데 이 파일은 Google doc로 해당 Row에 맞게 올라가야지 나중에 관리가 된다. 따라서  일단 이것을 같은 Row에 해당 스트링을 찾아서 옆에 ar 스트링을 자동으로 입력하고 그것을 output.xlsx로 만들어서 구글 독스로 붙여넣기를 하기로 했다.  

import 'dart:io';
import 'package:xml/xml.dart';
import 'package:excel/excel.dart';


//  안드로이드 strings.xml 에서 <string name="ok">OK</string>  ok-> OK 의 map을 만들어 리턴한다.
Map<String, String> loadStringsXml(String filename) {
  final Map<String, String> map = {};

  final file = File(filename);
  final document = XmlDocument.parse(file.readAsStringSync());
  final resources = document.getElement('resources')!;

    for (var node in resources.children) {
      final key = node.getAttribute('name');
      if (key?.isNotEmpty == true) {
        final text = node.text;
        map.putIfAbsent(key!, () => text);
      }
    }

  return map;
}


void writeXlsxFromStringsXml() {
  final Map<String, String> arStringMap = loadStringsXml('ar-strings.xml');
  final  outputExcel = Excel.createExcel();
  final  outputSheet = outputExcel['Sheet1']; // default sheet

  var file = "Screenshot_touch_translation.xlsx";
  var bytes = File(file).readAsBytesSync();
  var excel = Excel.decodeBytes(bytes);

  //Screenshot_touch_translation has below datas
  //flutter: [Data([common], 0, 7, null, sheet1), null, null, null, null, null, null, null, null, null, null]
  //flutter: [Data(ok, 0, 8, null, sheet1), null, null, null, Data(OK, 4, 8, null, sheet1), Data(확인, 5, 8, null, sheet1), Data(ОК, 6, 8, null, sheet1), Data(OK, 7, 8, null, sheet1), Data(TAMAM, 8, 8, null, sheet1), null, Data(Aceptar, 10, 8, null, sheet1)]
  //flutter: [Data(cancel, 0, 9, null, sheet1), null, null, null, Data(Cancel, 4, 9, null, sheet1), Data(취소, 5, 9, null, sheet1), Data(Отмена, 6, 9, null, sheet1), Data(Abbrechen, 7, 9, null, sheet1), Data(Vazgeç, 8, 9, null, sheet1), null, Data(Cancelar, 10, 9, null, sheet1)]
  var skipRow = true;
  for (var table in excel.tables.keys) {
    print(table); //sheet Name
    final t = excel.tables[table]!;
    print(t.maxCols);
    print(t.maxRows);
    for (var row in t.rows) {
      final keyData = row.elementAt(0);
      if (keyData == null ) continue;
      final key = keyData.value.toString();

      if (key == "[common]" || key == "[app]") {
        skipRow = false;
        continue;
      }
      if (skipRow) continue;

      final valueData = row.elementAt(4);
      if (valueData == null) continue;
      final value = valueData.value.toString();

      int rowIndex = keyData.rowIndex;
      outputSheet.updateCell(CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: rowIndex), key);
      outputSheet.updateCell(CellIndex.indexByColumnRow(columnIndex: 1, rowIndex: rowIndex), value);
      if (arStringMap.containsKey(key)) {
        String? arValue = arStringMap[key];
        outputSheet.updateCell(CellIndex.indexByColumnRow(columnIndex: 2, rowIndex: rowIndex), arValue);
      } else {
        // string-ar/ value not exists.
        print("$key-> $value");
      }
    }
  } //for

  // save result excel
  var fileBytes  = outputExcel.save()!;
  File("output.xlsx")
    ..createSync(recursive: true)
    ..writeAsBytesSync(fileBytes);

    print("save OK");
}

 

두 번째 구글 독스에서 xlsx를 내려받아서 res/values-XX에 strings.xml 파일을 자동으로 만드는 코드다. 


class StringItem {
  String key;
  String text;
  StringItem({required this.key, required this.text});
}


void writeStringsFromXlsx() {
  var file = "Screenshot_touch_translation.xlsx";
  var bytes = File(file).readAsBytesSync();
  var excel = Excel.decodeBytes(bytes);
 
    // first table
    final t = excel.tables["sheet1"]!;
    print(t.maxCols);
    print(t.maxRows);

    const langRow = 6;
    const firstLangCol = 4;
    const maxLangColumn = 10; //ar


    int column = firstLangCol;
    while(column <= maxLangColumn)  {
      var data =  t.cell(CellIndex.indexByColumnRow(columnIndex:column, rowIndex:langRow));
      String langCode = data.value.toString();
      print("langCode=$langCode");

      List<StringItem> baseStringList = List.empty(growable: true);
      List<StringItem> appStringList = List.empty(growable: true);

      int state = 0;
      for (var row in t.rows) {
        final keyData = row.elementAt(0);
        if (keyData == null) continue;
        final key = keyData.value.toString();

        if (key == "[common]") {
          state = 1;
          continue;
        }
        if (key == "[app]") {
          state = 2;
          continue;
        }
        if (key == "--end--") {
          break;
        }
        if (state == 0) continue;

        final valueData = row.elementAt(column);
        if (valueData == null) continue;
        final value = valueData.value.toString();

        // 반드시 포함 되어야 하는 데이타 체크
        final includeData = row.elementAt(3);
        if (includeData != null) {
          String includeStrings = includeData.value.toString();
          if (includeStrings.isNotEmpty) {
            for(var inc in includeStrings.split(' ')) {
              if (!value.contains(inc)) {
                print("ERROR: $langCode $key $inc $value");
              }
            }
          }
        }

        if (state == 1) {
          baseStringList.add(StringItem(key: key, text: value));
        } else {
          appStringList.add(StringItem(key: key, text: value));
        }
      }
      writeStringsXml(baseStringList, appStringList, langCode);
      column ++;
    }
  } // for  



// 안드로이드 res 폴더에 strings.xml 파일을 생성합니다. 
void writeStringsXml(List<StringItem> baseStringList, List<StringItem> appStringList, String langCode) {
  String langCodeSuffix;
  if (langCode == "en") {
    langCodeSuffix = ""; 
  }else {
    langCodeSuffix = "-$langCode";
  }

  final appResdir = "../../screenshot/app/src/main/res/values$langCodeSuffix";
  final baseResdir = "../../screenshot/android_base/res/values$langCodeSuffix";

  // android_base
  {
    final builder = XmlBuilder();
      builder.processing('xml', 'version="1.0" encoding="UTF-8"');
      builder.element("resources", nest: () {
        for (var element in baseStringList) {
          builder.element("string", nest: () {
            builder.attribute("name", element.key);
            final newText = touchText(langCode, element.key, element.text);
            if (element.key.endsWith("_cdata")) {
              builder.cdata(newText);
            } else {
              builder.text(newText);
            }
          });
        }
      });
      //
      final document = builder.buildDocument();
      String xmlString = document.toXmlString(pretty: true, indent: '    ', newLine: '\n');
      String path = "$baseResdir/strings.xml";
      File(path).writeAsStringSync(xmlString); 
      print(path);
  }
  // app
  {
    final builder = XmlBuilder();
      builder.processing('xml', 'version="1.0" encoding="UTF-8"');
      builder.element("resources", nest: () {
        for (var element in appStringList) {
          builder.element("string", nest: () {
            builder.attribute("name", element.key);
            final newText = touchText(langCode, element.key, element.text);
            if (element.key.endsWith("_cdata")) {
              builder.cdata(newText);
            } else {
              builder.text(newText);
            }
          });
        }
      });
      //
      final document = builder.buildDocument();
      String newLine = langCode == "en" ? "\r\n" : "\n";
      String xmlString = document.toXmlString(pretty: true, indent: '    ', newLine: newLine);
      String path = "$appResdir/strings.xml";
      File(path).writeAsStringSync(xmlString); 
      print(path);
  }
}

String touchText(String langCode, String key, String text) {
  Map<String, String> replaceMap = {};
  replaceMap["\${num}"] = "%d";
  replaceMap["\${str}"] = "%s";
  replaceMap["\${num1}"] = "%1\$d";
  replaceMap["\${num2}"] = "%2\$d";
  replaceMap["\${num3}"] = "%3\$d";
  replaceMap["\${num4}"] = "%4\$d";
  replaceMap["\${str1}"] = "%1\$s";
  replaceMap["\${str2}"] = "%2\$s";
  replaceMap["\${str3}"] = "%3\$s";
  replaceMap["\${str4}"] = "%4\$s";  
  
  replaceMap["'"] = "\\'";
  replaceMap['"'] = '\\"';
  
  if (langCode != "ar") {
    replaceMap["\n"] = "\\n";    
  }

  if (text.contains("\r\n")) {
    text = text.replaceAll("\r\n", "\\n");
  }

  if (key.endsWith("_cdata") == false && text.contains("%")) {
    text = text.replaceAll("%", "%%");
  }

  replaceMap.forEach((key, value) {
    if (text.contains(key)) {
      text = text.replaceAll(key, value);
    }
  });

  return text;
}

 

실행해 보기

curl -Ls https://docs.google.com/spreadsheets/d/blabla/export\?format\=xlsx > Screenshot_touch_translation.xlsx 
dart run lt_lang_utils.dart

 

https://dart.dev/tools/dart-compile 로 실행파일을 만들어서 실행 할 수도 있겠다. 

 

top

posted at

2022. 11. 29. 21:28


POST : Backend study

YouTrack or Trac 설치하기..

간단한 BTS나 설치해 보려고 검색했더니 방탄소년단만 검색되서 당황스럽네..  YouTrack이라는게 좋아보여서 설치시도했는데 잘 안 됬다. 그냥 옛날에 쓰던거 Trac을 다시 깔았다. 

YouTrack

https://www.jetbrains.com/help/youtrack/server/youtrack-docker-installation.html

를 참고해서 YouTrack을 오라클 클라우드에 vm 설치해본다.

데이타 스토리지가 /mnt/djkim에 마운트 되어 있기 때문에 필요한 폴더를 여기에 생성한다. 

sudo rm -rf /mnt/djkim/youtrack

sudo mkdir -p -m 750 /mnt/djkim/youtrack/data  /mnt/djkim/youtrack/logs /mnt/djkim/youtrack/conf /mnt/djkim/youtrack/backups

sudo chown -R 13001:13001 /mnt/djkim/youtrack/data /mnt/djkim/youtrack/logs /mnt/djkim/youtrack/conf /mnt/djkim/youtrack/backups

youtrack/docker-compose.yml

services:
  youtrack:
    image: jetbrains/youtrack:2022.2.62127
    ports:
      - '443:8080'
    volumes:
      - /mnt/djkim/youtrack/data:/opt/youtrack/data
      - /mnt/djkim/youtrack/conf:/opt/youtrack/conf
      - /mnt/djkim/youtrack/logs:/opt/youtrack/logs
      - /mnt/djkim/youtrack/backups:/opt/youtrack/backups

실행

[opc@instance-20221029-2034 youtrack]$ docker-compose  up -d
Creating network "youtrack_default" with the default driver
Pulling youtrack (jetbrains/youtrack:2022.2.62127)...
2022.2.62127: Pulling from jetbrains/youtrack
e5ae68f74026: Already exists
7d46bcaf99f5: Pull complete
ddad9f4a6fd0: Pull complete
abdff3063569: Pull complete
1c970f380745: Pull complete
c93de64a3086: Pull complete
9648adc387d1: Pull complete
e8ae72fd00fc: Pull complete
Digest: sha256:640a6244171e98125ca5e62260e019da505a9dab3a50c49f44daed559668f357
Status: Downloaded newer image for jetbrains/youtrack:2022.2.62127
Creating youtrack_youtrack_1 ... done

좀 있다가 log를 보면 마지막에 초기화를 위한 접속 url을 확인할 수 있다. 그곳으로 접속한다. 

[opc@instance-20221029-2034 youtrack]$ docker logs --tail 10 youtrack_youtrack_1
* JetBrains YouTrack 2022.2 runtime environment is successfully configured
* Loading logging configuration from /opt/youtrack/lib/ext/log4j.xml
* Redirecting JetBrains YouTrack 2022.2 logging to /opt/youtrack/logs/internal/services/bundleProcess
* Configuring Service-Container[bundleProcess]
* Configuring Bundle Backend Service
* Configuring Configuration Wizard
* Starting Service-Container[bundleProcess]
* Starting Bundle Backend Service
* Starting Configuration Wizard
* JetBrains YouTrack 2022.2 Configuration Wizard will listen inside container on {0.0.0.0:8080}/ after start and can be accessed by URL [http://<put-your-docker-HOST-name-here>:<put-host-port-mapped-to-container-port-8080-here>//?wizard_token=BlablaToken]

 

오라클 network에 80과 443은 열어두었다. youtrack은 443으로 연결되지만 프로토콜은 http 이다.   
따라서 http://<YOUR_IP or DOMAIN>:443//?wizard_token=BlablaToken 
으로 접속한다. 

사이트는 열리고 초기화 페이지가 진행되고 YouTrack Configurator 와 Hub는 실행이 되는데 YouTrack은 실행중 오류가 난다. 이유는 모르겠다. 2022.3 버전 과 2022.2 버전 모두 에러가 나고 /mnt/djkim 폴더를 연결하지 않아도 에러가 난다..  내부 오류 같은데..  문제 해결 아직 못함.. 서버 로그를 봐도 나오지 않는다. 자바 기반이라 좀 무거운 것 같은데. 그래서 OOM이라도 난 건가 

 

Trac 

https://registry.hub.docker.com/r/mastermindg/trac-ubuntu 여기를 참고해서 설치한다. 트랙  1.0.1이 설치된다.. 

최초 설치와 실행  

sudo rm -rf ~/trac-storage/
docker run -d -p 443:80 --name my_trac -v ~/trac-storage:/trac -e TRAC_ADMIN_PASS=blablapwd -e TRAC_ADMIN_NAME=admin mastermindg/trac-ubuntu

이후 서버 종료와 재실행

docker stop my_trac

docker start my_trac

서버 확인

80과 443 포트를 열어두고 있다. 443 포토는 https 용이기는 하지만 실제로 서비스에서는 사용되지 않기 때문에 trac용으로 http로 사용한다. 따라서 접속은 http://<YOUR IP or Domain>:443/ 로 하면 사용할 수 있다.  

top

posted at

2022. 11. 24. 19:18


POST : Backend study

GCP -> Oracle 클라우드로 Mongo db 이전 스터디

구글 클라우드에서 운영중인 독푸딩 서비스가 있다.  도커를 모를 때라 서버에 필요한 프로그램을 한땀한땀 스크립트를 찾아가면서 설치를 했다. 오라클 클라우드로 API 서버와 DB서버를 이전하면서 도커를 이용했다.  도커만 설치되어 있다면 서버 프로그램을 간단히 실행하고 종료시킬 수 있게 되었다.  

GCP 접속하여 Mongo 백업

# ssh 접속
dajkim76@Kims-Mac-mini ~/backend/gcp % ssh -i gcp.key 34.170.xxx.yyy
Enter passphrase for key 'gcp.key':

# mongo 백업
dajkim76@base-backend-1:~$ mongodump --db prod-db --out mongo_221122

# 폴더 압축
dajkim76@base-backend-1:~$ tar cvzf mongo_221122.tar.gz mongo_221122/

dajkim76@base-backend-1:~$ ls -al mongo_221122.tar.gz
-rw-rw-r-- 1 dajkim76 dajkim76 288717 Nov 21 15:45 mongo_221122.tar.gz

 

압축파일 가져오기

dajkim76@Kims-Mac-mini ~/backend/gcp % sftp -i gcp.key 34.170.xxx.yyy
Enter passphrase for key 'gcp.key':
Connected to 34.170.xxx.yyy.
sftp> get mongo_221122.tar.gz
Fetching /home/dajkim76/mongo_221122.tar.gz to mongo_221122.tar.gz
/home/dajkim76/mongo_221122.tar.gz                                                                                  100%  282KB 280.5KB/s   00:01
sftp> exit

 

압축파일 오라클 vm에 업로드

dajkim76@Kims-Mac-mini ~/backend/oci % sftp -i oracle.key opc@132.145.xxx.yyy
Enter passphrase for key 'oracle.key':
Connected to 132.145.xxx.yyy.
sftp> put mongo_221122.tar.gz
Uploading mongo_221122.tar.gz to /home/opc/mongo_221122.tar.gz
mongo_221122.tar.gz                                                                                                 100%  282KB   2.8MB/s   00:00
sftp> exit

 

압축풀기

[opc@instance-20221029-2034 ~]$ tar xvzf mongo_221122.tar.gz

 

mongo 도커 컨테이너에 접속하여 db 복구하기

# 컨테이너 접속 
docker exec -it docker_mongo_1 bash

# Mongo 복구: 호스트의 mongo_221122 디렉토리의 상위 폴더를 volumes에 바인딩시킬것
mongorestore -u root -p example ./mongo_221122

# 몽고 쉘 접속
mongosh -u root -p example

# 프로덕션 db에 유저 생성
use prod-db
db.createUser( { user: "prod-user", pwd: "prod-pwd", roles: [ { role: "readWrite", db: "prod-db" }, ] } )

 

백엔드 도커 파일 최종 정리

dev.env 개발환경변수

MONGO_ROOT_PASSWORD=example
MONGO_STORAGE=~/mongo-storage

docker-compose-mongo.yml

version: '3.1'

services:

  mongo:
    image: mongo:6.0.2
    restart: always
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: ${MONGO_ROOT_PASSWORD}
    volumes:
      - ${MONGO_STORAGE}:/data/db
      - ./:/app
    working_dir: /app
    hostname: mongo-host
    ports:
      - 27017:27017
    networks:
      - backend


networks:
  backend:
    driver: bridge
    external: true

start-mongo.sh

docker-compose -f docker-compose-mongo.yml --env-file $1.env up -d

mongo 서버 실행

start-mongo.sh dev

서비스 운영 환경이라면 prod.env 파일을 만들어서 start-mongo.sh prod

서버 종료는

docker-compose -f docker-compose-mongo.yml stop

 

 

참고 사이트

https://nota.tistory.com/53

https://velog.io/@killi8n/Mongodb-%EB%8D%A4%ED%94%84%ED%95%98%EA%B3%A0-%EB%B0%B1%EC%97%85%ED%95%98%EA%B8%B0-gejmop2baz

https://mirrors.edge.kernel.org/pub/software/scm/git/docs/git-archive.html

top

posted at

2022. 11. 22. 01:29


POST : Backend study

[docker study] load balancer -> nginx -> deno -> mongo 까지 연결 테스트

[Backend study] - [docker study] 오라클 로드밸런서 <--> nginx <--> deno server

이전에 오라클 클라우드에서 로드 밸런서에서 deno 웹 서버까지 연결 테스트 했다. mongo를 docker 켄테이너로 설치하고 deno 서버에서 mongo 서버로 connect까지 해 본다. 

50GB volume을 /mnt/djkim 에 마운트했다.

이미 오라클 vm에 자체 block volume을 붙여놓았다. 50GB 용량을 가지고 있고, 이것은 어떤 VM에도 attach될 수 있다. 나중에 vm이 업그레이드 될 경우에도 block volume을 붙이면 데이타 그대로 유지된다. 현재는 mongo db의 저장소로 사용할 예정이다. 

자세한 내용은  https://thekoguryo.github.io/oci/chapter03/6/3/ 를 참고했다.   

[연결할 명령어는 오래클 클라우드 vm의 볼륨 페이지의 ... 버튼에 있음]

[opc@instance-20221029-2034 ~]$ sudo iscsiadm -m node -o new -T iqn.2015-12.com.oracleiaas:46c16679-d0e5-4258-bb65-24260665245e -p 169.254.2.2:3260
New iSCSI node [tcp:[hw=,ip=,net_if=,iscsi_if=default] 169.254.2.2,3260,-1 iqn.2015-12.com.oracleiaas:46c16679-d0e5-4258-bb65-24260665245e] added
[opc@instance-20221029-2034 ~]$ sudo iscsiadm -m node -o update -T iqn.2015-12.com.oracleiaas:46c16679-d0e5-4258-bb65-24260665245e -n node.startup -v automatic
[opc@instance-20221029-2034 ~]$ sudo iscsiadm -m node -T iqn.2015-12.com.oracleiaas:46c16679-d0e5-4258-bb65-24260665245e -p 169.254.2.2:3260 -l
Logging in to [iface: default, target: iqn.2015-12.com.oracleiaas:46c16679-d0e5-4258-bb65-24260665245e, portal: 169.254.2.2,3260]
Login to [iface: default, target: iqn.2015-12.com.oracleiaas:46c16679-d0e5-4258-bb65-24260665245e, portal: 169.254.2.2,3260] successful.



[Disk /dev/sdb: 50 GiB, 확인]
[opc@instance-20221029-2034 ~]$ sudo fdisk -l
Disk /dev/sda: 46.6 GiB, 50010783744 bytes, 97677312 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 1048576 bytes
Disklabel type: gpt
Disk identifier: 347FC70E-7421-4C2C-82F1-CCEF19B84F66

Device       Start      End  Sectors  Size Type
/dev/sda1     2048   206847   204800  100M EFI System
/dev/sda2   206848  2303999  2097152    1G Linux filesystem
/dev/sda3  2304000 97675263 95371264 45.5G Linux LVM


Disk /dev/mapper/ocivolume-root: 35.5 GiB, 38088474624 bytes, 74391552 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 1048576 bytes


Disk /dev/mapper/ocivolume-oled: 10 GiB, 10737418240 bytes, 20971520 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 1048576 bytes


Disk /dev/sdb: 50 GiB, 53687091200 bytes, 104857600 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 1048576 bytes




[디스크 포맷할 파일포맷 확인]
[opc@instance-20221029-2034 ~]$ lsblk -f
NAME               FSTYPE      LABEL UUID                                   MOUNTPOINT
sda
|-sda1             vfat              3A04-27E1                              /boot/efi
|-sda2             xfs               92f46bb4-0609-4149-8155-521cb271344d   /boot
`-sda3             LVM2_member       eu3JyT-prH1-YQe4-5zOP-LDd1-fcTk-8XGRod
  |-ocivolume-root xfs               a2c46b56-da9f-4829-be87-1d87b18c8aeb   /
  `-ocivolume-oled xfs               f9d1a191-470c-4bb2-ad65-58f895357a5d   /var/oled
sdb



[포맷]
[opc@instance-20221029-2034 ~]$ sudo mkfs -t xfs /dev/sdb
meta-data=/dev/sdb               isize=512    agcount=4, agsize=3276800 blks
         =                       sectsz=4096  attr=2, projid32bit=1
         =                       crc=1        finobt=1, sparse=1, rmapbt=0
         =                       reflink=1
data     =                       bsize=4096   blocks=13107200, imaxpct=25
         =                       sunit=0      swidth=0 blks
naming   =version 2              bsize=4096   ascii-ci=0, ftype=1
log      =internal log           bsize=4096   blocks=25600, version=2
         =                       sectsz=4096  sunit=1 blks, lazy-count=1
realtime =none                   extsz=4096   blocks=0, rtextents=0


[포맷 확인]
[opc@instance-20221029-2034 ~]$ lsblk -f
NAME               FSTYPE      LABEL UUID                                   MOUNTPOINT
sda
|-sda1             vfat              3A04-27E1                              /boot/efi
|-sda2             xfs               92f46bb4-0609-4149-8155-521cb271344d   /boot
`-sda3             LVM2_member       eu3JyT-prH1-YQe4-5zOP-LDd1-fcTk-8XGRod
  |-ocivolume-root xfs               a2c46b56-da9f-4829-be87-1d87b18c8aeb   /
  `-ocivolume-oled xfs               f9d1a191-470c-4bb2-ad65-58f895357a5d   /var/oled
sdb                xfs               4a4f9fdc-1111-43ed-90a6-edc3d552aa08



[마운트할 디렉토리 생성]
[opc@instance-20221029-2034 ~]$ sudo mkdir /mnt/djkim



[/etc/fstab에 마지막 한 줄 추가]
opc@instance-20221029-2034 ~]$ sudo vi /etc/fstab 
/dev/oracleoci/oraclevdb /mnt/djkim xfs defaults,_netdev,nofail 0 2



[마운트하기]
[opc@instance-20221029-2034 ~]$ sudo mount -a



[디렉토리 확인]
[opc@instance-20221029-2034 djkim]$ ls -al /mnt/djkim
total 0
drwxr-xr-x. 2 root root  6 Nov 17 15:08 .
drwxr-xr-x. 3 root root 19 Nov 17 15:14 ..

 

mongo  서버 구동

mongo/docker-compose.yml

version: '3.1'

services:

  mongo:
    image: mongo:6.0.2
    restart: always
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example
    volumes:
      - /mnt/djkim/mongo-storage:/data/db
      - ./:/app
    working_dir: /app
    hostname: mongo-host
    ports:
      - 27017:27017
    networks:
      - backend


networks:
  backend:
    driver: bridge
    external: true

내가 붙인 50GB에 /mnt/djkim/mongo-storage에 mongo 서버 데이타 디렉토리로 지정한다.  호스트 이름은 mongo-host 로 지정한다.  현재 디렉토리도 /app 으로 연결한다.  

실행과 확인

[opc@instance-20221029-2034 mongo]$ docker-compose up -d
Recreating mongo_mongo_1 ... done
[opc@instance-20221029-2034 mongo]$ sudo docker ps
CONTAINER ID   IMAGE                  COMMAND                  CREATED          STATUS         PORTS                                           NAMES
eccd20973e1f   mongo:6.0.2            "docker-entrypoint.s…"   10 seconds ago   Up 6 seconds   0.0.0.0:27017->27017/tcp, :::27017->27017/tcp   mongo_mongo_1
c9a0b542887a   nginx:1.21.6           "/docker-entrypoint.…"   2 days ago       Up 2 days      0.0.0.0:80->80/tcp, :::80->80/tcp               nginx_nginx_1
42b92c017ed8   denoland/deno:1.28.0   "/tini -- docker-ent…"   2 days ago       Up 2 days      0.0.0.0:8080->8080/tcp, :::8080->8080/tcp       deno_service_deno_1
[opc@instance-20221029-2034 mongo]$ sudo docker exec -it mongo_mongo_1 bash
root@mongo-host:/app# pwd
/app

 

deno 서버에서 Mongo 연결

이전 포스트의 deno_service/app.ts 수정.. 딱히 하는 일은 없고 db 서버에 연결만 해본다. 

import { serve } from "https://deno.land/std@0.164.0/http/server.ts";
import { Bson, MongoClient } from "https://deno.land/x/mongo/mod.ts"

class MyMongoClient {
    dbName: string
    url: string
    client: MongoClient

    constructor(dbName: string, url: string) {
        this.dbName = dbName;
        this.url = url;
        this.client = {} as MongoClient;
    }

    async connect() {
        console.log("mongodb connecting ...")
        const client = new MongoClient();
        await client.connect(this.url);
        this.client = client;
        console.log("mongodb connected OK")
    }

    getDatabase() {
        return this.client.database(this.dbName);
    }
}


export const mongoClient = new MyMongoClient("test-db", "mongodb://root:example@mongo-host:27017")
await mongoClient.connect()


const port = 8080;

const handler = (request: Request): Response => {
  const body = `Your user-agent is:\n\n${
    request.headers.get("user-agent") ?? "Unknown"
  }`;

  return new Response(body, { status: 200 });
};

console.log(`HTTP webserver running. Access it at: http://localhost:8080/`);
await serve(handler, { port });

실행 확인

[opc@instance-20221029-2034 deno_service]$ docker-compose up -d
Starting deno_service_deno_1 ... done
[opc@instance-20221029-2034 deno_service]$ sudo docker ps
CONTAINER ID   IMAGE                  COMMAND                  CREATED          STATUS          PORTS                                           NAMES
eccd20973e1f   mongo:6.0.2            "docker-entrypoint.s…"   19 minutes ago   Up 18 minutes   0.0.0.0:27017->27017/tcp, :::27017->27017/tcp   mongo_mongo_1
c9a0b542887a   nginx:1.21.6           "/docker-entrypoint.…"   2 days ago       Up 2 days       0.0.0.0:80->80/tcp, :::80->80/tcp               nginx_nginx_1
42b92c017ed8   denoland/deno:1.28.0   "/tini -- docker-ent…"   2 days ago       Up 12 seconds   0.0.0.0:8080->8080/tcp, :::8080->8080/tcp       deno_service_deno_1

[opc@instance-20221029-2034 deno_service]$ sudo docker logs --tail 10 deno_service_deno_1
Download https://deno.land/x/web_bson@v0.2.5/src/validate_utf8.ts
Download https://deno.land/x/web_bson@v0.2.5/src/float_parser.ts
Download https://deno.land/x/mongo@v0.31.1/src/utils/saslprep/deps.ts
Download https://deno.land/x/mongo@v0.31.1/src/utils/saslprep/load_code_points.ts
Download https://deno.land/x/mongo@v0.31.1/src/utils/saslprep/sparse_bitfield.ts
Download https://deno.land/x/mongo@v0.31.1/src/utils/saslprep/memory_pager.ts
mongodb connecting ...
mongodb connected OK
HTTP webserver running. Access it at: http://localhost:8080/
Listening on http://localhost:8080/

mongodb connected OK가 찍힌걸로 봐서 연결까지 된 듯 하다. 

mongodb://root:example@mongo-host:27017 로 연결은 된 듯 하지만 실제로 읽기나 쓰기를 하면 auth 에러가 날 것이다.  test-db에 권한이 없다고 나올 텐데 권한을 추가하자..  https://dajkim76.tistory.com/560 참고

[opc@instance-20221029-2034 deno_service]$ docker ps
CONTAINER ID   IMAGE                  COMMAND                  CREATED          STATUS          PORTS                                           NAMES
eccd20973e1f   mongo:6.0.2            "docker-entrypoint.s…"   46 minutes ago   Up 46 minutes   0.0.0.0:27017->27017/tcp, :::27017->27017/tcp   mongo_mongo_1
c9a0b542887a   nginx:1.21.6           "/docker-entrypoint.…"   2 days ago       Up 2 days       0.0.0.0:80->80/tcp, :::80->80/tcp               nginx_nginx_1
42b92c017ed8   denoland/deno:1.28.0   "/tini -- docker-ent…"   2 days ago       Up 27 minutes   0.0.0.0:8080->8080/tcp, :::8080->8080/tcp       deno_service_deno_1
[opc@instance-20221029-2034 deno_service]$ docker exec -it mongo_mongo_1 bash

root@mongo-host:/app# mongosh -u root -p example
Current Mongosh Log ID:	63765dee3755cc7187c2c247
Connecting to:		mongodb://<credentials>@127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+1.6.0
Using MongoDB:		6.0.2
Using Mongosh:		1.6.0

For mongosh info see: https://docs.mongodb.com/mongodb-shell/

------
   The server generated these startup warnings when booting
   2022-11-17T15:25:14.257+00:00: /sys/kernel/mm/transparent_hugepage/enabled is 'always'. We suggest setting it to 'never'
   2022-11-17T15:25:14.257+00:00: vm.max_map_count is too low
------

------
   Enable MongoDB's free cloud-based monitoring service, which will then receive and display
   metrics about your deployment (disk utilization, CPU, operation statistics, etc).

   The monitoring data will be available on a MongoDB website with a unique URL accessible to you
   and anyone you share the URL with. MongoDB may use this information to make product
   improvements and to suggest MongoDB products and deployment options to you.

   To enable free monitoring, run the following command: db.enableFreeMonitoring()
   To permanently disable this reminder, run the following command: db.disableFreeMonitoring()
------

test> use test-db
switched to db test-db
test-db> db.createUser( { user: "testuser", pwd: "testpwd", roles: [ { role: "readWrite", db: "test-db" }, ] } )
{ ok: 1 }
test-db>

이제 mongodb://testuser:testpwd@mongo-host:27017 로 다시 연결을 만들고 read/write를 해보면 문제 없다.  테스트 해보지는 않았지만 아마도 될 듯.

테스트해 보자.. mongosh로 데이타를  하나 넣었다..

test> use test-db
switched to db test-db
test-db> db.user_list.insertOne({name:"name1", age:31})
{
  acknowledged: true,
  insertedId: ObjectId("63766d72b310e7e91006854b")
}
test-db>

app.ts 다시 수정. 쓸데없는 코드 다 빼고 핵심만 보이도록 수정했다. https://deno.land/x/mongo@v0.31.1/tests/cases/02_connect.ts?source. 참고

import { serve } from "https://deno.land/std@0.164.0/http/server.ts";
import { Bson, MongoClient } from "https://deno.land/x/mongo/mod.ts"

export const mongoClient = new MongoClient();
await mongoClient.connect("mongodb://testuser:testpwd@mongo-host:27017/test-db")

interface UserSchema {
  _id: ObjectId;
  name: string;
  age: number;
}


const userCollection = mongoClient.database().collection<UserSchema>("user_list");
const cursor = userCollection.find();
const usersResult = await cursor.toArray();
const json = JSON.stringify(usersResult);
console.log(json);


const port = 8080;

const handler = (request: Request): Response => {
  const body = `Your user-agent is:\n${json}\n${
    request.headers.get("user-agent") ?? "Unknown"
  }`;

  return new Response(body, { status: 200 });
};

console.log(`HTTP webserver running. Access it at: http://localhost:8080/`);
await serve(handler, { port });

실행
[opc@instance-20221029-2034 deno_service]$ docker-compose up -d
Starting deno_service_deno_1 ... done


결과

dajkim76@Kims-Mac-mini ~ %  curl https://oci.mdiwebma.com/deno-app
Your user-agent is:
[{"_id":"63766d72b310e7e91006854b","name":"name1","age":31}]
curl/7.79.1

https://oci.mdiwebma.com/deno-app. 누르면 오라클 클라우드 로드밸랜서 -> VM Nginx -> Deno app 서버 -> mongo db 순으로 요청 흐름이 발생하고 test-db의 user_list 콜렉션을 전부 가져와서 응답한다.  nginx, deno, mongo는 모두 docker container로 동작한다. 

top

posted at

2022. 11. 18. 01:04


CONTENTS

Seize the day
BLOG main image
김대정의 앱 개발 노트와 사는 이야기
RSS 2.0Tattertools
공지
아카이브
최근 글 최근 댓글
카테고리 태그 구름사이트 링크