Seize the day

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

POST : Backend study

오라클 클라우드 무료 vm 테스트 하기..

Oracle vm 만들기는 아래 두 페이지를 참고

OCI 퀵스타트(Step by Step) :: 오라클 클라우드 사용자 가이드 (taewan.kim)
https://www.youtube.com/watch?v=wLgHEn2vcPo 

마지막에 ssh로 접속하는게 잘 안 되었는데 
로컬에 있는 ssh-keygen 으로 생성하여 pub키를 등록한 경우  
접속은 ssh -i my_private.key opc@PUBLIC_IP
우분투인 경우는 opc 대신에 ubuntu 계정을 사용하면 된다.

무료 티어가 vm 2개 평생 무료가 가능하다, 
무료 티어가 타사 최소 사양의 유료 vm 사양보다 성능이 좋은 것 같다.  
무료 퍼브릭 고정IP가 할당할 수 있는 듯.
200GB HDD가 제공된다.  
매월 10TB 네트웍 제공..
Cloud Free Tier | Oracle 대한민국 참고..
개발 하거나 소규모 서비스를 운영하기에는 최적인 듯하다. 

 

 

docker, docker-compose 설치는 아래 두 페이지를 참고

How to fix Error starting docker service Unit not found? | Jhooq
CentOS 7, Docker 및 Docker Compose 설치하기 (tistory.com)

Oracle 클라우드의 기본 Oracle LInux는 Centos 명령어로 해도 되는 듯..

# 이전 버전 제거, 처음 설치라면 skip
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine 



# yum-utils
sudo yum install -y yum-utils 



# docker 리포 업데이트
sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo 
    

# docker comunity edition 설치
sudo yum install docker-ce docker-ce-cli containerd.io


# 도커 서비스 시작
sudo systemctl start docker 


# docker test 
sudo docker run hello-world


# doker.socket 관련 에러가 나면
sudo vi /usr/lib/systemd/system/docker.socket 
해서.. [Unit]부분 Description 아래에 
PartOf=docker.service
한 줄 추가 .. 위 사이트 참고..


# vi 저장하고 도커 재시작
sudo systemctl daemon-reload
sudo systemctl start docker.socket
sudo systemctl start docker  


# 리눅스 부팅시 도커 시작하기
sudo systemctl enable docker.service
sudo systemctl enable containerd.service 


# docker-compose 설치
sudo curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose


# docker-compose 실행 권한 부여
sudo chmod +x /usr/local/bin/docker-compose


# 설치된 docker-compose 실행 확인, sudo 안 붙여도 되나??
docker-compose --version
docker-compose version 1.27.4, build 40524192

 

 

 

docker로 Nginx 돌리기, vm http 외부 접속 설정..

$ sudo docker run --name nginx2 -d -p 80:80 nginx:1.21.6



# 접속은 성공..
$ curl localhost
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
....

mac에서 접속해보기..  오라클 VM의 네트워크 방화벽 정책을 변경해야 한다. 
30 오라클클라우드 80, 443포트 열기 (pythonblog.co.kr)        참고해서 80, 443 포트 열기
크롬에서 http://PUBLIC_IP  접속하면 welcom Nginx 페이지를 볼 수 있다..

 

 

 

top

posted at

2022. 10. 29. 22:50


POST : Backend study

docker container간에 네트워크 설정 - study

awesome-compose/nginx-nodejs-redis at master · docker/awesome-compose · GitHub

 

GitHub - docker/awesome-compose: Awesome Docker Compose samples

Awesome Docker Compose samples. Contribute to docker/awesome-compose development by creating an account on GitHub.

github.com

여기의 docker를 생플을 가지고 스터디를 진행한다. 

nginx/Dockerfile을 사용하여 이미지를 생성하지 않고 docker hub의 이미지로 컨테이너만 만들어서 돌려보자..

/docker-compose.yml 의 내용을 일부 아래처럼 수정하면 된다...
Before:

  nginx:
    build: ./nginx
    ports:
    - '80:80'
    depends_on:
    - web1
    - web2

After: nginx의 버전을 명기하는게 좋겠다.

  nginx:
    image: nginx:1.21.6
    ports:
      - '80:80'
    depends_on:
      - web1
      - web2
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf

 

nginx를 다른 compose 파일로 변경해보자.. nginx는 proxy 서버이므로 수정될 일이 없으므로 개발 단계에서 매번 start /stop을 반복할 필요가 없다. 

/nginx/docker-compose.yml  생성

services:
  nginx:
    image: nginx:1.21.6
    ports:
      - '80:80'
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf

 

nginx-nginx-1 | 2022/10/20 16:37:39 [emerg] 1#1: host not found in upstream "web1:5000" in /etc/nginx/conf.d/default.conf:2
nginx-nginx-1 | nginx: [emerg] host not found in upstream "web1:5000" in /etc/nginx/conf.d/default.conf:2

nginx.conf에 정의되어 있는 web1, web2 호스트를 를 찾을 수 없다는 에러..

/nginx/docker-compose.yml 수정

services:
  nginx:
    image: nginx:1.21.6
    ports:
      - '80:80'
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
    networks:
      - backend

networks:
  backend:
    driver: bridge
    external: true
    internal: true

nodejs를 돌리는 compose 와 네트워크를 공유해야 하므로 backend 네트워크를 생성하고,  netwoks: 로 backend 지정
# docker network create backend

그래도 에러나네... 아마도 web1, web2가 실행중이지 않아서 인듯..

nginx-nginx-1 | 2022/10/20 16:51:18 [emerg] 1#1: host not found in upstream "web1:5000" in /etc/nginx/conf.d/default.conf:2
nginx-nginx-1 | nginx: [emerg] host not found in upstream "web1:5000" in /etc/nginx/conf.d/default.conf:2

 

/docker-compose.yml 수정

services:
  redis:
    image: 'redislabs/redismod'
    ports:
      - '6379:6379'
    networks:
      - backend
  web1:
    restart: on-failure
    build: ./web
    hostname: web1
    ports:
      - '81:5000'
    networks:
      - backend
  web2:
    restart: on-failure
    build: ./web
    hostname: web2
    ports:
      - '82:5000'
    networks:
      - backend

networks:
  backend:
    driver: bridge
    external: true
    internal: true

두개의 node 서버와 redis를  묶은 compose를 실행한 후..  nginx/docker-compose.yml를 실행하면 정상동작한다..

proxy 용 nginx 
app1용 node 서버
app2용 node 서버
app1과 app2가 동시에 사용하는 mongodb 
이런식으로 4개의 켄테이너를 각각 띄울 수 있게되었고. 순서대로 한다면 db -> app1 -> app2 -> nginx 실행시키면 문제 없이 동작 할 듯..

 

Docker overview | Docker Documentation 도커 공부는 이걸로 시작.

Docker Compose와 버전별 특징 : NHN Cloud Meetup (toast.com)

Multi container apps | Docker Documentation

 

top

posted at

2022. 10. 21. 02:02


POST : Backend study

docker로 mongo db + node 개발환경 만들기

mongo - Official Image | Docker Hub

Docker를 사용하여 MongoDB 설치하고 접속하기 | PoiemaWeb

도커(Docker)로 MongoDB 서버 구축하기 - 정우일 블로그 (wooiljeong.github.io)

 

vi docker-compose.yml

# Use root/example as user/password credentials
version: '3.1'

services:

  mongo:
    image: mongo
    restart: always
    container_name: mongo-dev
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example
    volumes:
      - ~/mongo-storage:/data/db
    ports:
      - 27017:27017

  mongo-express:
    image: mongo-express
    restart: always
    ports:
      - 27018:8081
    environment:
      ME_CONFIG_MONGODB_ADMINUSERNAME: root
      ME_CONFIG_MONGODB_ADMINPASSWORD: example
      ME_CONFIG_MONGODB_URL: mongodb://root:example@mongo:27017/
    depends_on:
      - mongo

호스트의 ~/mongo-storage  가 mongo서버의 데이타 폴더이다. 컨테이너가 삭제되더라도 데이타는 유지된다. 
depends_on으로 mongo 서비스가 먼저 실행되도록 한다. 

# 서버 시작
% docker-compose up -d

# localhost:27018 로 express 동작 확인

# 서버 종료
% docker-compose down

Node 서버를 붙여보자.

개발 환경에서 js 파일  변경시 서버 재시작을 위해서 nodemon을 설치한다. 

npm install nodemon --save-dev

package.json. 이전에 대충 만든것 재활용..

{
  "name": "hello_docker",
  "version": "1.0.0",
  "description": "hello docker test",
  "main": "app.js",
  "scripts": {
    "start": "node app.js",
    "dev": "nodemon app.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "nodemon": "^2.0.20"
  }
}

npm run dev 로 실행하면 nodemon 으로 실행한다. 

docker-compose.yml

# Use root/example as user/password credentials
version: '3.1'

services:

  mongo:
    image: mongo
    restart: always
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example
    volumes:
      - ~/mongo-storage:/data/db
    ports:
      - 27017:27017

  mongo-express:
    image: mongo-express
    restart: always
    ports:
      - 27018:8081
    environment:
      ME_CONFIG_MONGODB_ADMINUSERNAME: root
      ME_CONFIG_MONGODB_ADMINPASSWORD: example
      ME_CONFIG_MONGODB_URL: mongodb://root:example@mongo:27017/
    depends_on:
      - mongo

  node:
    image: node:16.18
    command: sh -c "npm install && npm run dev"
    ports:
      - 7700:8000
    working_dir: /app
    volumes:
      - ./:/app
    depends_on:
      - mongo

node 가 추가됬다. 

node는 애플리케이션 서버이므로 나중에 mongo에 시작과 동시에 접속하므로 mongo가 먼저 실행되고 나서 실행된다.   현재 디렉토리를 컨테이너의 /app 으로 바인딩하여  app.js등 모든 앱 소스를 호스트 pc에서 관리한다. 호스트의 7700 포트를 컨테이너 8000포트로 바인딩한다.

localhost:7700 으로 확인시 동작확인하고, app.js 파일 수정후 저장시 자동으로 노드 재시작되어 바로 브라우저에서 확인할 수있다.  이런 식으로 개발한다면 호스트에 노드랑 몽고db를 설치하지 않아도 될 것 같다. 

 

Node서버에서 Mongodb에 연결해 보자

mongoose 라이브러리 설치
npm install mongoose  

node 컨테이너에서 mongo 컨테이너로 연결가능하도록 links를 추가한다.  Networking in Compose | Docker Documentation

  node:
    image: node:16.18
    command: sh -c "npm install && npm run dev"
    ports:
      - 7700:8000
    working_dir: /app
    volumes:
      - ./:/app
    depends_on:
      - mongo
    links:
      - "mongo:database"

database는 호스명으로 사용된다.  때문에 "mongodb://database:27017/testdb"  를 사용..

const http = require('node:http');
const mongoose = require('mongoose');


// Node의 native Promise 사용
mongoose.Promise = global.Promise;
const MONGO_URI = "mongodb://database:27017/testdb";

mongoose.connect(MONGO_URI)
  .then(() => console.log('Successfully connected to mongodb'))
  .catch(e => console.error(e));

const hostname = '0.0.0.0';
const port = 8000;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello, World!\n');
});

server.listen(port, hostname, () => {
  console.log(`Node Server running at http://${hostname}:${port}/`);
});

docker-compose up -d 를 실행하면 mongo 에 접속 성공했음 로그가 나온다..
[nodemon] starting `node app.js`
Node Server running at http://0.0.0.0:8000/
Successfully connected to mongodb

links 추가 없이 const MONGO_URI = "mongodb://mongo:27017/testdb"; 로 수정해도 접속이 됬다. 서비스 이름을 호스트 이름으로 사용할 수 있나보다.

이제부터 비주얼 스튜디오 코드로 열심히 개발하고,,,  js 파일 저장하면 개발 서버는 자동으로 재시작되고, localhost:27018 로 접속해서 db를 바로  확인할 수 있는 환경이 구축되었다.

 

GitHub - docker/awesome-compose: Awesome Docker Compose samples

 

GitHub - docker/awesome-compose: Awesome Docker Compose samples

Awesome Docker Compose samples. Contribute to docker/awesome-compose development by creating an account on GitHub.

github.com

 

top

posted at

2022. 10. 20. 00:57


POST : Backend study

docker image 만들기 테스트

$ mkdir hello_docker
$ cd hello_docker
$ npm init
$ cat package.json

{
  "name": "hello_docker",
  "version": "1.0.0",
  "description": "hello docker test",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

 

$ vi app.js

const http = require('node:http');

const hostname = '0.0.0.0';
const port = 8000;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello, World!\n');
});

server.listen(port, hostname, () => {
  console.log(`Node Server running at http://${hostname}:${port}/`);
});

 

$ vi Dockerfile

# Docker Hub에 있는 node의 최신 LTS(장기 지원) 버전인 16을 사용할 것입니다.
FROM node:16.18

# 컨테이너 앱 디렉터리 생성
WORKDIR /app

# 앱 의존성 설치
# 가능한 경우(npm@5+) package.json과 package-lock.json을 모두 복사하기 위해
# 와일드카드를 사용
COPY package*.json ./

RUN npm install
# 프로덕션을 위한 코드를 빌드하는 경우
# RUN npm ci --only=production

# 앱 소스 추가
# .dockerignore 작성할 것
COPY . .

# 앱이 8000포트에 바인딩 되어 있으므로 EXPOSE 지시어를 사용해서 docker 데몬에 매핑합니다.
EXPOSE 8000

# 서버 시작
CMD [ "node", "app.js" ]

# docker build . -t djkim/hello_docker
# docker run --name test1 -d -p 8000:8000 djkim/hello_docker

vi .dockerignore
node_modules
npm-debug.log

# hello_docker 라는 850메가 도커 이미지 생성
$ docker build . -t djkim/hello_docker

# 도커 컨테이너 test1을 생성하고 실행한다. 서버 포트 8000을 컨테이너 포트 8000으로 바잉딩
$ docker run --name test1 -d -p 8000:8000 djkim/hello_docker

# test 
$ curl http://localhost:8000
Hello, World!

% docker logs test1
Node Server running at http://0.0.0.0:8000/

간단한 서비스의 배포를 서버까지 다 포함시킨 이미지를 만들어서 배포하는 것은 문제가 있어서 docker-compose 를 공부할 것.. 

 

자습서: Visual Studio Code에서 Docker 앱 시작 | Microsoft Learn

 

자습서: Visual Studio Code에서 Docker 앱 시작

이 자습서에서는 VS Code에서 Docker 사용을 시작하는 방법을 알아봅니다. Azure에 앱을 만들고 배포합니다.

learn.microsoft.com

 

top

posted at

2022. 10. 19. 23:55


POST : Android Dev Study

ScopedStorage 모드 테스트

ScopedStoage 모드에서 아무런 권한이 없어도 MediaStore api를 사용하지 않고도, Pictures 폴더에 이미지 저장이 가능한가? 

App Target sdk 31
Android 11 에서 테스트
AndroidManifest에 어떤한 권한이나, element 수정이 없이 기본 값으로 테스트

@RequiresApi(Build.VERSION_CODES.Q)
private fun onClickButton1(v: View) {
    val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
    val filename = System.currentTimeMillis().toString() + ".jpg"
    val file = File(dir, filename)
    val bitmap = viewToBitmap(binding.text1)
    saveBitmap(bitmap, file)
    scanMediaFile(file)
    Toast.makeText(requireContext(), "isScopedStorage=${!Environment.isExternalStorageLegacy()}", Toast.LENGTH_SHORT).show()
}

private fun viewToBitmap(view: View): Bitmap {
    val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
    val canvas = Canvas(bitmap)
    view.layout(view.left, view.top, view.right, view.bottom)
    view.draw(canvas)
    return bitmap
}

private fun saveBitmap(bitmap: Bitmap, file: File) {
    val fos = FileOutputStream(file)
    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos)
    fos.flush();
    fos.close();
}

private fun scanMediaFile(file: File) {
    val mediaScanIntent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
    val contentUri: Uri = Uri.fromFile(file)
    mediaScanIntent.data = contentUri
    requireContext().sendBroadcast(mediaScanIntent)
}

결과는
isScopedStorage=true, 이미지 저장도 잘 되고, OS 갤러리에 이미지도 잘 표시된다.
관련 기술 문서는 :
Storage updates in Android 11  |  Android Developers

Access files using direct file paths and native libraries

To help your app work more smoothly with third-party media libraries, Android 11 allows you to use APIs other than the MediaStore API to access media files from shared storage using direct file paths. These APIs include the following:
  • The File API.
  • Native libraries, such as fopen().

Access media files from shared storage  |  Android Developers

Direct file paths

To help your app work more smoothly with third-party media libraries, Android 11 (API level 30) and higher allow you to use APIs other than the MediaStore API to access media files from shared storage. You can instead access media files directly using either of the following APIs:
  • The File API.
  • Native libraries, such as fopen().
If you don't have any storage-related permissions, you can access files in your app-specific directory, as well as media files that are attributed to your app, using the File API.
If your app tries to access a file using the File API and it doesn't have the necessary permissions, a FileNotFoundException occurs.
To access other files in shared storage on a device that runs Android 10 (API level 29), it's recommended that you temporarily opt out of scoped storage by setting requestLegacyExternalStorage to true in your app's manifest file. In order to access media files using native files methods on Android 10, you must also request the READ_EXTERNAL_STORAGE permission.



WRITE_EXTERNAL_STORAGE  퍼미션은 어떻게 응답하는가?

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
private val permissions = arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE)

private fun onClickFav(v: View) {
    ActivityCompat.requestPermissions(this, permissions, 100)
}

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    if (requestCode == 100) {
        permissions.forEachIndexed { index, permission ->
            val isGranted = grantResults[index] == PackageManager.PERMISSION_GRANTED
            Log.e("__T", "$permission -> isGranted = $isGranted")
        }
    }
}

결과는 두 개다 isGranted = true, 그렇다고 sd카드 root에 폴더를 만들수 있는 것은 아니다. 그럴려면 MANAGE_EXTERNAL_STORAGE  권한이 필요. 어쨌든 WRITE_EXTERNAL_STORAGE 퍼미션에 android:maxSdkVersion=" "을  추가해 놓은 예제를 봐서.. maxSdk SDK_INT에 따라서 WRITE_EXTERNAL_STORAGE를 제외하지 않아도 될 것 같다. 
관련 기술 문서는: 
Storage updates in Android 11  |  Android Developers

Target any version

The following changes take effect in Android 11, regardless of your app's target SDK version:
  • The Storage runtime permission is renamed to Files & Media.
  • If your app hasn't opted out of scoped storage and requests the READ_EXTERNAL_STORAGE permission, users see a different dialog compared to Android 10. The dialog indicates that your app is requesting access to photos and media, as shown in Figure 1.
    Users can see which apps have the READ_EXTERNAL_STORAGE permission in system settings. On the Settings > Privacy > Permission manager > Files and media page, each app that has the permission is listed under Allowed for all files. If your app targets Android 11, keep in mind that this access to "all files" is read-only. To read and write to all files in shared storage using this app, you need to have the all files access permission.

Target Android 11

If your app targets Android 11, both the WRITE_EXTERNAL_STORAGE permission and the WRITE_MEDIA_STORAGE privileged permission no longer provide any additional access.
Keep in mind that, on devices that run Android 10 (API level 29) or higher, your app can contribute to well-defined media collections such as MediaStore.Downloads without requesting any storage-related permissions. Learn more about how to request only the necessary permissions when working with media files in your app.
top

posted at

2022. 2. 5. 21:28


POST : Android Dev Study

간단한 UrlDownloader 구현

URL 이미지 혹은 파일을 특정 디렉토리에 다운로드하는 간단한 클래스를 만들었다.  간단하지만 예외 케이스에 최대한 대응하도록 견고하게 구현했다. 

OkHttp3

implementation("com.squareup.okhttp3:okhttp:4.9.0")

 

AndroidManifest

<uses-permission android:name="android.permission.INTERNET" />

 

UrlDownloader

import androidx.annotation.WorkerThread
import java.io.File
import java.io.IOException
import okhttp3.OkHttpClient
import okhttp3.Request
import okio.sink

class UrlDownloader(
    private val url: String,
    private val headers: Map<String, String>?,
    private val file: File,
) {
    @WorkerThread
    @Throws(Exception::class)
    fun download(): File {
        val destDirectory: File = checkNotNull(file.parentFile)
        if (!destDirectory.exists()) {
            destDirectory.mkdirs()
        }
        val tempFile = File.createTempFile("temp_", null, destDirectory)
        
        try {
            downloadToTempFile(tempFile)
            if (!tempFile.renameTo(file)) {
                throw IOException("Failed rename to target file")
            }
            return file
        } catch (ex: Exception) {
            tempFile.delete()
            throw ex
        }
    }

    private fun downloadToTempFile(tempFile: File) {
        val request = Request.Builder()
            .url(url)
            .addHeaders()
            .build()
        
        okHttpClient.newCall(request).execute().use { response ->
            if (!response.isSuccessful) {
                throw IOException("invalid response code: ${response.code}")
            }
            tempFile.sink().use {
                checkNotNull(response.body).source().readAll(it)
            }
        }
    }

    private fun Request.Builder.addHeaders() = apply {
        headers?.entries?.forEach {
            addHeader(it.key, it.value)
        }
    }
    
    companion object {
        private val okHttpClient = OkHttpClient()
    }
}

 

Test code

private fun test() {
    val url =
        "https://1.bp.blogspot.com/-73nbn4r0Imk/YKMtQF8ZPlI/AAAAAAAAQjs/TzvtHpXiFNcQJUM-ltLvyFRv7ezLwpWugCLcBGAsYHQ/s0/Untitled.jpeg"
    val file = File(externalCacheDir, "dir4/2.jpg")

    Thread {
        UrlDownloader(url, headers = null, file).download()
    }.start()
}
top

posted at

2022. 1. 28. 16:32


CONTENTS

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