Flutter 대표 카카오톡 개발자 톡을 운영중입니다.

https://open.kakao.com/o/gsshoXJ

 

Flutter 개발자 모임

#flutter #android #ios #안드로이드 #아이폰 #모바일 #선물요정소환

open.kakao.com


자바스크립트에서 많이 사용되는 Promise.all 을 다트에선 어떻게 구하는지 알아볼 예정입니다.

Promise.all 은 여러개의 Promise 를 모아서 한꺼번에 처리해주는 역할을 해준다. 

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

 

Promise.all()

Promise.all() 메서드는 순회 가능한 객체에 주어진 모든 프로미스가 이행한 후, 혹은 프로미스가 주어지지 않았을 때 이행하는 Promise를 반환합니다. 주어진 프로미스 중 하나가 거부하는 경우, 첫 번째로 거절한 프로미스의 이유를 사용해 자신도 거부합니다.

developer.mozilla.org

그럼 다트에서는 어떻게 사용되는 지 살펴보겠다. 

void main() {
  final f1 = getValue(delay : 1, value: 1);
  final f2 = getValue(delay : 5, value: 2);
  final f3 = getValue(delay : 2, value: 3);
  final f4 = getValue(delay : 3, value: 4);
  
  Future.wait([f1,f2,f3,f4])
     .then((value) => print(value));
  
}

Future<int> getValue({int delay, int value}) => Future.delayed(Duration(seconds: delay), () => Future.value(value));

 문제는 저건 순서대로 진행될텐데 병렬로 할려면 어떻게 하는지 궁금 하신 분은(Isolate사용) 아래 링크에서 보시길 바랍니다.

https://buildflutter.com/flutter-threading-isolates-future-async-and-await/

 

Flutter Threading: Isolates, Future, Async And Await - Build Flutter

Flutter applications start with a single execution process to manage executing code. Inside this process you will find different ways that the process handles multiple pieces of code executing at the same time. Isolates When Dart starts, there will be one

buildflutter.com

이 내용은 https://www.udemy.com/course/vue-js-course 를 공부해서 정리한 내용입니다.

개발완료 화면은 다음과 같습니다.

Input을 넣으면 텍스트에 맞게끔 이미지가 생성됩니다. 

vue는 선언형 형태로 개발되며 다음 세가지 flow를 가진다고 한다. 

  • Data : 데이터 초기화
  • Compute : 주어진 데이터를 조합해서 리턴해줍니다.
  • Method : 만들어진 데이터를 이용해서 랜더링해서 화면을 다시 보여줍니다. 

Html

<div id="app">
  <h1>My identicon generator</h1>
  <div>
    Input:
    <Input v-on:input="onInput" />    
  </div>  
  <div>
    Output:
    <div v-html="identicon"></div>
  </div>  
</div>  

Javascript

new Vue({
  el: '#app',
  data: {
    textInput: ''
  },
  computed: {
    identicon: function() {
      return jdenticon.toSvg(this.textInput, 200);
    }
  },
  methods: {
    onInput: function(event) {
      this.textInput = event.target.value;
    }
  }
})

이상입니다.

저장소 정리하다 보니 1월에 진행된 간단한 백엔드 연동된 스터디 자료가 있어서 공유합니다. 

내용 : 간단한 투두 리스트

구현내용
1. Flutter
2. Bloc
3. RxDart
4. Docker
5. Node, Express
6. nginx proxy

https://github.com/bear2u/flutter-ecommerce-study

 .final__clip-image-1 {
     -webkit-clip-path: circle(50% at 50% 50%);
             clip-path: circle(50% at 50% 50%);
   }


   .final__clip-image-2 {
     -webkit-clip-path: polygon(0% 0%,
                                100% 0%,
                                100% 75%,
                                75% 75%,
                                75% 100%,
                                50% 75%,
                                0% 75%);
             clip-path: polygon(0% 0%,
                                100% 0%,
                                100% 75%,
                                75% 75%,
                                75% 100%,
                                50% 75%,
                                0% 75%);
   }


   .final__clip-image-3 {
     -webkit-clip-path: polygon(20% 0%,
                                0% 20%,
                                30% 50%,
                                0% 80%,
                                20% 100%,
                                50% 70%,
                                80% 100%,
                                100% 80%,
                                70% 50%,
                                100% 20%,
                                80% 0%,
                                50% 30%);
             clip-path: polygon(20% 0%,
                                0% 20%,
                                30% 50%,
                                0% 80%,
                                20% 100%,
                                50% 70%,
                                80% 100%,
                                100% 80%,
                                70% 50%,
                                100% 20%,
                                80% 0%,
                                50% 30%);
   }


   .final__clip-image-4 {
     -webkit-clip-path: polygon(0% 20%,
                                40% 20%,
                                40% 0%,
                                100% 50%,
                                40% 100%,
                                40% 80%,
                                0% 80%);
             clip-path: polygon(0% 20%,
                                40% 20%,
                                40% 0%,
                                100% 50%,
                                40% 100%,
                                40% 80%,
                                0% 80%);
   }

.shadow-typo {
        position: relative;
        display: inline-block;
        font-size: 4em;
        text-transform: uppercase;
        color: #00b3b4;
        text-shadow: 3px 3px 0px #e7eef1, 8px 8px 0px rgba(0, 0, 0, 0.1);
   }

   .threedee-typo {
        font-size: 4em;
        text-transform: uppercase;
        color: #f2395a;
        -webkit-transform: skew(-5deg, -5deg) rotate(5deg);
                transform: skew(-5deg, -5deg) rotate(5deg);
        -webkit-transform-origin: center center;
                transform-origin: center center;
        text-shadow: 1px 1px #d10e31,
                    2px 2px #d10e31,
                    3px 3px #d10e31,
                    4px 4px #d10e31,
                    5px 5px #d10e31,
                    6px 6px #d10e31,
                    7px 7px #d10e31,
                    8px 8px #890920,
                    9px 9px #890920,
                    10px 10px #890920,
                    11px 11px 15px rgba(0,0,0,0.5);
  }

Day2

Sexy Typograping

 

'스터디 > WEB' 카테고리의 다른 글

Clipping CSS - Day3  (0) 2019.05.22
Css next grid 클론 ( 1 ~ 5 )  (0) 2018.11.17
PostCSS 스터디 정리 ( 2주차 )  (0) 2018.11.12
Web 디자인시 유의 할 점 정리  (0) 2018.11.09
CSS Next 정리 ( 부산 1주차 HTML + CSS 스터디 )  (0) 2018.11.07

Flutter 화면 제작 공부

https://www.youtube.com/watch?v=dMLreUXpSQ0&t=1s

를 보고 하나씩 공부 하고자 한다.

우선 사이즈 부분을 알아보자.

1553055691923

  • Row를 사용해서 정렬
  • Container Boxdecoration을 이용해서 박스형태 제작
  • 그림자 효과 적용

소스

높이 자동 계산 로직

double baseHeight = 640.0;

double screenAwareSize(double size, BuildContext context) {
  return size * MediaQuery.of(context).size.height / baseHeight;
}
import 'package:flutter/material.dart';
import 'package:flutter_adidas_shoes_exam/utils.dart';
import 'data.dart';

void main() => runApp(MaterialApp(
      home: MyApp(),
      debugShowCheckedModeBanner: false,
    ));

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

List<String> sizeNumlist = [
  "7",
  "8",
  "9",
  "10",
];

class _MyAppState extends State<MyApp> {
  int _currentSizeIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      body: Padding(
        padding: const EdgeInsets.all(30.0),
        child: Column(
          children: <Widget>[
            Row(
              children: sizeNumlist.map((item) {
                var index = sizeNumlist.indexOf(item);
                return GestureDetector(
                    child: sizeItem(item, _currentSizeIndex == index, context),
                    onTap: (){
                      setState(() {
                        _currentSizeIndex = index;
                      });
                    },
                );
              }).toList(),
            )
          ],
        ),
      ),
    );
  }
}

Widget sizeItem(String item, bool isSelected, BuildContext context) {
  return Padding(
    padding: EdgeInsets.only(left: 8.0),
    child: Container(
      width: screenAwareSize(30.0, context),
      height: screenAwareSize(30.0, context),
      decoration: BoxDecoration(
        color: isSelected ? Color(0xFFFC3930) : Color(0xFF525663),
        borderRadius: BorderRadius.circular(5.0),
        boxShadow: [
          BoxShadow(
            color: isSelected ? Colors.black.withOpacity(.5) : Colors.black12,
            offset: Offset(0.0, 10.0),
            blurRadius: 10.0
          )
        ]
      ),
      child: Center(
        child: Text(
          item,
          style: TextStyle(color: Colors.white, fontFamily: "Montserrat-Bold", fontSize: 11.0),
        ),
      ),
    ),
  );
}

Clipper를 이용한 뷰

1553060095143

위와 같이 구성할려면 알아야 할 내용

  • Clip
  • BoxShadow
  • BoxDecoration
class MClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    var path = Path();
      //1  
    path.lineTo(0.0, size.height);
      //2  
    path.lineTo(size.width * 0.5, size.height);
      //3
    path.lineTo(size.width, size.height * 0.2);
      //4
    path.lineTo(size.width, 0.0);
    path.close();
    return path;
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) {
    return true;
  }
}

Path는 펜슬로 위치를 잡아가면서 그려주는 역할을 해준다.

1553060539050

그림이 상당히 이상하지만 순서대로 따라가면서 이렇게 그려주면서 다각형(?)을 그려준다.

전체 소스

import 'package:flutter/material.dart';
import 'package:flutter_adidas_shoes_exam/utils.dart';
import 'data.dart';

void main() => runApp(MaterialApp(
      home: MyApp(),
      debugShowCheckedModeBanner: false,
    ));

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

  int _currentColorIndex = 0;

  List<Widget> colorSelector() {
    List<Widget> colorItemList = new List();
    for (var i = 0; i < colors.length; i++) {
      colorItemList
          .add(colorItem(
          colors[i],
          i == _currentColorIndex,
          context,
              () {
            setState(() {
              _currentColorIndex = i;
            });
          }
      ));
    }

    return colorItemList;
  }

  Widget colorItem(Color color, bool isSelected, BuildContext context,
      VoidCallback _ontab) {
    return Padding(
      padding: EdgeInsets.all(16.0),
      child: Container(
        width: screenAwareSize(30.0, context),
        height: screenAwareSize(30.0, context),
        decoration: BoxDecoration(
            color: Colors.black,
            borderRadius: BorderRadius.circular(5.0),
            boxShadow: [
              BoxShadow(
                  color: Colors.black.withOpacity(.8),
                  blurRadius: 10.0,
                  offset: Offset(0.0, 10.0)
              )
            ]
        ),
        child: ClipPath(
          clipper: MClipper(),
          child: Container(
            width: double.infinity,
            height: double.infinity,
            color: color,
          ),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Container(
        width: double.infinity,
        margin: EdgeInsets.only(left: screenAwareSize(20.0, context)),
        child: Row(
          children: colorSelector(),
        ),
      ),
    );
  }

}

class MClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    var path = Path();
    path.lineTo(0.0, size.height);
    path.lineTo(size.width * 0.5, size.height);
    path.lineTo(size.width, size.height * 0.2);
    path.lineTo(size.width, 0.0);
    path.close();
    return path;
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) {
    return true;
  }
}

다음에는 프로그래스바를 이용해서 커스텀 뷰를 공부해볼 예정이다.

부산에서 매주 진행되는 스터디입니다.

부산에서 다른 스터디 내용을 보실려면 카페 에서 보실수 있습니다.

https://www.udemy.com/docker-and-kubernetes-the-complete-guide 을 공부하고 있습니다

도커 & 쿠버네티스 9주차 스터디 

pod 자세한 설명 스크립트

# kubectl describe <object type> <object name>

> kubectl describe pod client-pod

...........

Name:         client-pod
Namespace:    default
Node:         minikube/10.0.2.15
Start Time:   Sat, 02 Feb 2019 12:05:16 +0900
Labels:       component=web
Annotations:  kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"labels":{"component":"web"},"name":"client-pod","namespace":"default"},"spec":{"container...
Status:       Running
IP:           172.17.0.16
Containers:
  client:
    Container ID:   docker://465ecbe522f537a36c26c021d88c1efb21782daf1e6fffd1e93be3469701a4d5
    Image:          bear2u/multi-worker
    Image ID:       docker-pullable://bear2u/multi-worker@sha256:6559ad68144e14b8f6f3054ab0f19056853ea07a7c4ead068d9140bd0a33b926
    Port:           3000/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Sat, 09 Feb 2019 10:24:04 +0900
    Last State:     Terminated
      Reason:       Completed
      Exit Code:    0
      Started:      Sat, 09 Feb 2019 10:06:12 +0900
      Finished:     Sat, 09 Feb 2019 10:24:01 +0900
    Ready:          True
    Restart Count:  3
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-28mbg (ro)
Conditions:
  Type           Status
  Initialized    True
  Ready          True
  PodScheduled   True
Volumes:
  default-token-28mbg:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-28mbg
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type    Reason                 Age               From               Message
  ----    ------                 ----              ----               -------
  Normal  Scheduled              6d                default-scheduler  Successfully assigned client-pod to minikube
  Normal  SuccessfulMountVolume  6d                kubelet, minikube  MountVolume.SetUp succeeded for volume "default-token-28mbg"
  Normal  Pulling                6d                kubelet, minikube  pulling image "bear2u/multi-client"
  Normal  Pulled                 6d                kubelet, minikube  Successfully pulled image "bear2u/multi-client"
  Normal  Created                6d                kubelet, minikube  Created container
  Normal  Started                6d                kubelet, minikube  Started container
  Normal  SuccessfulMountVolume  12h               kubelet, minikube  MountVolume.SetUp succeeded for volume "default-token-28mbg"
  Normal  SandboxChanged         12h               kubelet, minikube  Pod sandbox changed, it will be killed andre-created.
  Normal  Pulling                12h               kubelet, minikube  pulling image "bear2u/multi-client"
  Normal  Pulled                 12h               kubelet, minikube  Successfully pulled image "bear2u/multi-client"
  Normal  Created                12h               kubelet, minikube  Created container
  Normal  Started                12h               kubelet, minikube  Started container
  Normal  SuccessfulMountVolume  25m               kubelet, minikube  MountVolume.SetUp succeeded for volume "default-token-28mbg"
  Normal  SandboxChanged         25m               kubelet, minikube  Pod sandbox changed, it will be killed andre-created.
  Normal  Pulling                25m               kubelet, minikube  pulling image "bear2u/multi-client"
  Normal  Pulled                 25m               kubelet, minikube  Successfully pulled image "bear2u/multi-client"
  Normal  Killing                7m                kubelet, minikube  Killing container with id docker://client:Container spec hash changed (3635549375 vs 3145631940).. Container will be killed and recreated.
  Normal  Pulling                7m                kubelet, minikube  pulling image "bear2u/multi-worker"
  Normal  Created                7m (x2 over 25m)  kubelet, minikube  Created container
  Normal  Pulled                 7m                kubelet, minikube  Successfully pulled image "bear2u/multi-worker"
  Normal  Started                7m (x2 over 25m)  kubelet, minikube  Started container

업데이트 오류

만약 pod 설정파일에서 containerPort를 변경시 어떻게 되는지 보자

> kubectl apply -f client-pod.yaml

.......

the Pod "client-pod" is invalid: spec: Forbidden: pod updates may not change fields other than `spec.containers[*].image`, `spec.initContainers[*].image`, `spec.activeDeadlineSeconds` or `spec.tolerations` (only additions to existing tolerations)
{"Volumes":[{"Name":"default-token-28mbg","HostPath":null,"EmptyDir":null,"GCEPersistentDisk":null,"AWSElasticBlockStore":null,"GitRepo":null,"Secret":{"SecretName":"default-token-28mbg","Items":null,"DefaultMode":420,"Optional":null},"NFS":null,"ISCSI":null,"Glusterfs":null,"PersistentVolumeClaim":null,"RBD":null,"Quobyte":null,"FlexVolume":null,"Cinder":null,"CephFS":null,"Flocker":null,"DownwardAPI":null,"FC":null,"AzureFile":null,"ConfigMap":null,"VsphereVolume":null,"AzureDisk":null,"PhotonPersistentDisk":null,"Projected":null,"PortworxVolume":null,"ScaleIO":null,"StorageOS":null}],"InitContainers":null,"Containers":[{"Name":"client","Image":"bear2u/multi-worker","Command":null,"Args":null,"WorkingDir":"","Ports":[{"Name":"","HostPort":0,"ContainerPort":

A: 9999,"Protocol":"TCP","HostIP":""}],"EnvFrom":null,"Env":null,"Resources":{"Limits":null,"Requests":null},"VolumeMounts":[{"Name":"default-token-28mbg","ReadOnly":true,"MountPath":"/var/run/secrets/kubernetes.io/serviceaccount","SubPath":"","MountPropagation":null}],"VolumeDevices":null,"LivenessProbe":null,"ReadinessProbe":null,"Lifecycle":null,"TerminationMessagePath":"/dev/termination-log","TerminationMessagePolicy":"File","ImagePullPolicy":"Always","SecurityContext":null,"Stdin":false,"StdinOnce":false,"TTY":false}],"RestartPolicy":"Always","TerminationGracePeriodSeconds":30,"ActiveDeadlineSeconds":null,"DNSPolicy":"ClusterFirst","NodeSelector":null,"ServiceAccountName":"default","AutomountServiceAccountToken":null,"NodeName":"minikube","SecurityContext":{"HostNetwork":false,"HostPID":false,"HostIPC":false,"ShareProcessNamespace":null,"SELinuxOptions":null,"RunAsUser":null,"RunAsGroup":null,"RunAsNonRoot":null,"SupplementalGroups":null,"FSGroup":null},"ImagePullSecrets":null,"Hostname":"","Subdomain":"","Affinity":null,"SchedulerName":"default-scheduler","Tolerations":[{"Key":"node.kubernetes.io/not-ready","Operator":"Exists","Value":"","Effect":"NoExecute","TolerationSeconds":300},{"Key":"node.kubernetes.io/unreachable","Operator":"Exists","Value":"","Effect":"NoExecute","TolerationSeconds":300}],"HostAliases":null,"PriorityClassName":"","Priority":null,"DNSConfig":null}

B: 3000,"Protocol":"TCP","HostIP":""}],"EnvFrom":null,"Env":null,"Resources":{"Limits":null,"Requests":null},"VolumeMounts":[{"Name":"default-token-28mbg","ReadOnly":true,"MountPath":"/var/run/secrets/kubernetes.io/serviceaccount","SubPath":"","MountPropagation":null}],"VolumeDevices":null,"LivenessProbe":null,"ReadinessProbe":null,"Lifecycle":null,"TerminationMessagePath":"/dev/termination-log","TerminationMessagePolicy":"File","ImagePullPolicy":"Always","SecurityContext":null,"Stdin":false,"StdinOnce":false,"TTY":false}],"RestartPolicy":"Always","TerminationGracePeriodSeconds":30,"ActiveDeadlineSeconds":null,"DNSPolicy":"ClusterFirst","NodeSelector":null,"ServiceAccountName":"default","AutomountServiceAccountToken":null,"NodeName":"minikube","SecurityContext":{"HostNetwork":false,"HostPID":false,"HostIPC":false,"ShareProcessNamespace":null,"SELinuxOptions":null,"RunAsUser":null,"RunAsGroup":null,"RunAsNonRoot":null,"SupplementalGroups":null,"FSGroup":null},"ImagePullSecrets":null,"Hostname":"","Subdomain":"","Affinity":null,"SchedulerName":"default-scheduler","Tolerations":[{"Key":"node.kubernetes.io/not-ready","Operator":"Exists","Value":"","Effect":"NoExecute","TolerationSeconds":300},{"Key":"node.kubernetes.io/unreachable","Operator":"Exists","Value":"","Effect":"NoExecute","TolerationSeconds":300}],"HostAliases":null,"PriorityClassName":"","Priority":null,"DNSConfig":null}

image-20190209104247175

  • 이미지만 변경할수 있다는 걸 유념하자

Deployment

기존 포드형태에서는 이미지말고는 변경이 안된다. 이걸 극복하기 위해서 Depoyment라는 개념을 하나 더 추가를 하자

Deployment에서는 Pod 설정을 가지고 있다.

pod 에서 포트를 변경시 Deployment 에서는 포트를 죽이고 새로운 포트를 올린다.

그럼 새 파일을 만들어서 설정 파일을 만들자

client-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: client-deployment
spec:
  replicas: 1
  selector: 
    matchLabels:
      component: web
  template:
    metadata:
      labels:
        component: web
    spec:
      containers:         
        - name: client
          image: bear2u/multi-client
          ports:
            - containerPort: 3000 
  • template 밑에 포드를 구성
  • replicas 를 1개이상 설정시 템플릿을 여러개 만든다

image-20190209113138607

설정 파일 삭제

기존 설정 파일 삭제를 할 때 사용되는 스크립트

kubectl delete -f client-pod.yaml

10초 정도 후에 확인을 해보자

kubectl get pods

...
no resources found

새로운 deployment 설정 파일 적용

$ kubectl apply -f client-deployment.yaml
deployment.apps "client-deployment" configured
  • pods -> deployments 로 명령어가 변경이 되는 점 유의
$ kubectl get deployments
NAME                  DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
client-deployment     1         1         1            1           56d
  • pods 확인시 deployment 로 자동 생성되는 걸 확인 가능하다
$ kubectl get pods
NAME                                  READY     STATUS    RESTARTS   AGE
client-deployment-848b54d879-ch26z    1/1       Running   5          56d
  • 이미지를 바꿔서 새롭게 deployment 에서 포드가 변경되는 걸 보자
$ kubectl get pods
NAME                                  READY     STATUS              RESTARTS   AGE
client-deployment-848b54d879-ch26z    1/1       Running             5          56d
client-deployment-89bb69575-54pnn     0/1       ContainerCreating   0          5s
$ kubectl get pods
NAME                                  READY     STATUS    RESTARTS   AGE
client-deployment-89bb69575-54pnn     1/1       Running   0          43s
  • 자세한 설명을 보여주는 명령어
$ kubectl get pods -o wide
NAME                                  READY     STATUS    RESTARTS   AGE       IP            NODE
client-deployment-89bb69575-54pnn     1/1       Running   0          7m        172.17.0.1   minikube

image-20190209120048329

kubectl describe pods client-deployment
  • deployment.yaml 에서 replica를 변경시 숫자를 주목하자
...
replicas: 5
....
$ kubectl apply -f client-deployment.yaml

$ kubectl get deployment
NAME                  DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
client-deployment     5         3         3            1           56d
  • 하나씩 하나씩 올라가는 걸 확인 할 수 있다.

도커 업데이트 이미지 배포

만약 도커내 이미지가 업데이트 되는 경우 배포를 하는 경우에 대해서 알아보자

  • 도커 허브에 푸시
  • 마지막 이미지를 다시 가져와서 배포
kubectl apply -f client-deployment.yaml

nothing changed

하지만 이미 변경된 내용이 없기 때문에 적용이 안된다. 이 부분을 해결하기 위해서는 다음 3가지의 방법이 있을 수 있다

  • 수동으로 설정을 삭제한다 (비추천)
  • 태그별로 이미지를 만들어서 업데이트 진행(많이 쓰는 편)
    • 이미지가 변경되기 때문에 새로운 포드가 올라가고 업데이트를 진행한다
    • ex) bear2u/multi-client:v1...v2
  • 선언형 명령어로 업데이트 선언

새로운 업데이트 단계

  1. 도커 이미지 태그별로 생성

    docker build -t bear2u/multi-client:v2 .
    
  2. 도커 허브로 푸시

    docker push <tag>
    
  3. 쿠버네티스 업데이트 명령어로 실행

    # kubectl set image <object_type> / <object_name> <container_name> = <new image to use>
    
    $ kubectl set image deployment/client-deployment client=bear2u/multi-client:v2
    
  4. 업데이트 서버 테스트

    http://192.168.99.100:31515
    

image-20190209130349946

image-20190209131039743

  • 현재 로컬에 있는 도커와 쿠버네티스(미니큐브)는 다른 가상서버라서 도커가 분리되서 공유되지 않는다

해당 미니큐브 서버로 접속하기 위해서는 eval 명령어를 이용할 수 있다. (도커 환경 설정이 가능하다)

실행시 미니큐브에 있는 도커 콘테이너를 볼수 있다

$ eval $(minikube docker-env)
$ docker ps
........
CONTAINER ID        IMAGE                        COMMAND                  CREATED             STATUS PORTS                                                                NAMES
5551a5172d45        bear2u/multi-client          "nginx -g 'daemon of…"   14 minutes ago      Up 14 minutes                                                                      k8s_client_client-deployment-747d8c754-kncgq_default_17678d42-2c1f-11e9-8634-08002761bac3_0
5d2e22a0c7c9        k8s.gcr.io/pause-amd64:3.1   "/pause"                 14 minutes ago      Up 14 minutes                                                                      k8s_POD_client-deployment-747d8c754-kncgq_d
$ minikube docker-env

export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.100:2376"
export DOCKER_CERT_PATH="/Users/bear2u/.minikube/certs"
export DOCKER_API_VERSION="1.35"
# Run this command to configure your shell:
# eval $(minikube docker-env)

그럼 이 과정이 필요한 이유는 ?

  • 직접 쿠버네티스 노드 안에 도커 로그를 확인할 경우

  • 쿠버네티스내 캐싱된 도커들 정리할 경우

    • docker system prune -a
      

kubectl 로 minikube 도커로 접속가능

kubectl exec -it <docker image name>
kubectl logs <docker image name>

결론

오늘 공부한 내용은 다음과 같다.

  • 포드에 대한 설명 및 실행 스크립트
  • 업데이트시 하는 방법에 대해서 고민
  • Pods와 Depolyment와의 관계
  • minikube 내 도커 시스템 접속 및 관리
  • 설정 파일 삭제 방법

이상으로 9주차 도커 & 쿠버네티스 스터디 정리 내용이다. 참석해주셔서 감사합니다.

부산에서 매주 진행되는 스터디입니다.

부산에서 다른 스터디 내용을 보실려면 카페 에서 보실수 있습니다.

https://www.udemy.com/docker-and-kubernetes-the-complete-guide 을 공부하고 있습니다

Kubernetes

minikube start

상태확인

minikube status

클러스터 정보 확인

kubectl cluster-info

목표

다양한 도커 이미지들을 활용해서 로컬 쿠버네티스를 통해서 올리는 게 목표

도커 컴포즈와 쿠버네티스 비교

  • docker-compose
    • 이미지들을 각각 빌드해서 올린다
    • 우리가 원하는 컨테이너를 만든다
    • 각 네트워크 속성들에 대해 정의를 각각 한다.
  • kubernetes
    • 이미 모든 이미지들이 만들어져있다고 가정한다
    • 우리가 만들기를 원하는 하나의 오브젝트에 하나의 설정을 준비한다
    • 우리는 모든 네트워크를 수동으로 설정해야 한다

우리는 도커 허브에 이미 올라가있는 이미지를 가져와서 쿠버네티스에서 설정하고 배포를 하는 과정을 진행을 할 예정이다.

포드 구성 파일 추가

  • docker-hub 에서 bear2u/multi-client 검색
  • 폴더를 새로 추가 (mkdir sample-k8s)
  • 설정 파일 추가
    • client-pod.yaml

apiVersion: v1 kind: Pod metadata: ## 1 name: client-pod labels: component: web spec: ## 2 containers: - name: client image: bear2u/multi-client ports: ## 3 - containerPort: 3000

  1. kind는 만들고자 하는 오브젝트 유형
  2. 다른 설정에서 불러와서 사용할 이름 정의
  3. 포드 내 콘테이너들 설정 정의를 한다
  4. 포드 내 콘테이너 포트를 정의를 한다. (내부용인걸 유념)

노드 설정 파일 추가

client-node-port.yaml

apiVersion: v1 kind: Service metadata: name: client-node-port spec: type: NodePort ports: # 1 - port: 3050 # 2 targetPort: 3000 # 3 nodePort: 31515 selector: # 4 component: web

서비스 형태의 설정파일을 추가한다.

  • kind : Service 정의해서 서비스 설정 파일인걸 체크
  1. port는 해당 서비스 포트를 뜻한다. 외부로 향하는 포트는 아니다.
  2. targetPort는 내부에 콘테이너 포트를 뜻한다.
  3. nodePort는 외부에서 접속시 들어올 수 있는 포트를 뜻한다.

현재 개발 서버이기 때문에 따로 RB(로드 발랜싱)은 지정하지 않는다.

설정파일

설정파일은 다음 4가지로 만들어질 수 있다.

  • StatefulSet
  • ReplicaController
  • Pod
  • Service

apiVersion은 다음 두가지 종류를 가진다.

image-20190208222316470

기본 적인 노드의 구성

image-20190208222519850

  • 포드는 하나이상의 콘테이너를 담고 있어야 한다
  • 하나의 포드에 여러개의 콘테이너를 담는 경우는 정말 긴밀하게 연결되어 있는 경우를 생각하자

설정 파일 타입

image-20190208223230713

바람직한 예

image-20190208222759200

구성도 (미니 큐브)

image-20190208223333452

image-20190208223526446

image-20190208223630053

설정 파일 선언

이제 설정 파일 두개를 kubectl 를 통해서 선언을 하도록 하자.

kubectl apply -f client-pod.yaml
kubectl apply -f client-node-port.yaml

pods & services

설정한 내용에 대해서 포드 동작되는 지 체크할려면 아래와 같이 할 수 있다

kubectl get pods

.......
client-pod                            1/1       Running   1          1h

서비스에 대해서도 가져올 수 있다

kubectl get services

........
client-node-port              NodePort    10.109.106.191   <none>        3050:31515/TCP   6d

웹 테스트

이제 서버에 동작이 잘 되는지 체크하자.

우선 IP를 가져오자. vm box를 통해서 가져와야 하기 때문에 머신을 통해야 한다

minikube ip

........
192.168.99.100

이제 실행해보자. 웹 브라우저에 다음과 같이 쳐서 들어갈 수 있다

http://192.168.99.100:31515/

전체 배포 흐름

image-20190208224323680

참고

  • docker ps 를 하면 현재 실행되고 있는 콘테이너들이 보인다. 그걸 죽이더라도 쿠버네티스가 다시 시작한다.
  • 선언형으로 이루어져서 운영자는 마스터에 설정파일을 적용하면 스케쥴로 인해 포드수를 서서히 올리거나 줄이도록 한다.
  • 물론 명령형으로 수동으로 올리거나 내릴순 있지만 추천은 안함


로그인 수업

목표

한빛 출반 홈페이지에 접속해서 로그인 과정을 거쳐서 마일리지 와 코인정보를 가져오는 게 목표

준비

  1. 파이썬
  2. vscode
  3. requests
  4. beautifulsoup

한빛 출반 네트워크 로그인 과정 뚫어보기

로그인 페이지 분석

<div class="w940 wrap_member">
		<!-- 로그인 영역 -->	
		<form name="frm" id="frm" action="#" method="post">
		<input name="retun_url" id="retun_url" type="hidden" value="http://www.hanbit.co.kr/index.html" class="i_text" size="100">
		<div class="login_left">		
			<fieldset>
				<legend>한빛출판네트워크 로그인</legend>
				
				<label class="i_label" for="login_id"><strong style="position: absolute; visibility: hidden;"></strong>
					<input name="m_id" id="m_id" type="text" value="" class="i_text" placeholder="아이디" onkeydown="javascript:if(event.keyCode==13){login_proc(); return false;}">
				</label> 

				<label class="i_label" for="login_pw"><strong style="position: absolute; visibility: hidden;"></strong>
					<input name="m_passwd" id="m_passwd" type="password" value="" class="i_text" placeholder="비밀번호" onkeydown="javascript:if(event.keyCode==13){login_proc(); return false;}">
				</label>
				
				<label>
					<input type="button" name="login_btn" id="login_btn" value="로그인" class="btn_login">					
				</label>
				
				<label class="i_label2">
					<input type="checkbox" name="keepid" id="keepid" value="1" class="i_check"><strong>아이디 저장</strong>
				</label>
			</fieldset>
			
			<ul class="login_btn">
				<li><a href="/member/find_id.html" class="btn_idc">아이디 찾기</a></li>
				<li><a href="/member/find_pw.html" class="btn_pwc">비밀번호 찾기</a></li>
				<li><a href="/member/member_agree.html" class="btn_joinc">회원가입</a></li>
			</ul>
		</div>
		</form>
		<!-- //로그인 영역 -->
		
		<!-- 배너 -->
		<div class="login_right"><a href="/event/current/current_event_view.html?hbe_idx=88&amp;page=0" target="_blank">
					<img src="/data/banner/login1901psd.jpg" alt="소중한 친구에게 손글씨 엽서와 한빛 책을 선물하세요!" width="440" height="220">
				</a></div>
		<!-- //배너 -->
	</div>

우선 로그인되는 과정을 분석하기 위해서는 크롬에서 network / doc 탭에서 흐름을 체크를 하자.

preserve log 라는 곳에 체크를 해야 한다. 이유는 network 페이지내에서는 한번 새로 고침을 하는 순간 로그들이 없어지기 때문에 이전 로그들을 남겨야 할려면 체크를 하자.

1548983019451

로그인을 하는 순간

1548983082001

login.html -> login_proc -> index.html 순으로 흐르게 된다. 기본적인 로그인 흐름을 볼수 있다.

1548983369350

Request 항목에서 Form Data를 살펴보면 id, password 등이 주고 받는 걸 볼수 있다.

소스 작성

그럼 이제 본격 파이썬으로 진행해보도록 하자.

우선 패키지 먼저 설치를 하자

pip install beautifulsoup4 urljoin load_dotenv

그럼 소스 흐름은

  1. 우선 login_proc 에 post로 값을 던져서 세션정보를 가져오도록한다.
  2. 마이 페이지로 접속 후 마일리지와 코인 정보를 가져온다.

.env 파일을 만들고 거기에 USER_ID와 USER_PASSWORD를 지정해주자

import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
from dotenv import load_dotenv
import os

# 환경 변수 로드
load_dotenv()

# 아이디와 비밀번호 지정

USER_ID = os.getenv("USER_ID")
USER_PWD = os.getenv("USER_PASSWORD")

# 세션 시작하기
session = requests.session()

# 로그인하기
login_info = {
    "m_id": USER_ID,
    "m_passwd": USER_PWD
}

url_login = "http://www.hanbit.co.kr/member/login_proc.php"
res = session.post(url_login, data=login_info)
# 오류 발생시 예외가 발생한다
res.raise_for_status()

# 마이페이지 접근
url_mypage = "http://www.hanbit.co.kr/myhanbit/myhanbit.html"
res = session.get(url_mypage)
res.raise_for_status()

# 마일리지와 이코인 가져오기
soup = BeautifulSoup(res.text, "html.parser")
mileage = soup.select_one(".mileage_section1 span").get_text()
ecoin = soup.select_one(".mileage_section2 span").get_text()
print("마일리지: " + mileage)
print("이코인: " + ecoin)

실행을 해보면 최초 가입시 받는 2,000 코인이 잘 나오는 걸 확인이 가능하다.

> python login_test.py

마일리지: 2,000
이코인: 0

결론

이상으로 아주 간단한 로그인 스크래핑을 진행했다. 이 스터디는 매주 금요일에 진행되는 파이썬 자동화 수업 중 내용입니다.

React,MongoDB,Express,Nginx 도커 개발 환경 구성하기

이번 시간에는 대중적으로 많이 쓰이는 환경을 도커로 하나씩 구축해볼 예정이다.

구성 스택은 다음과 같다.

  1. Client : React
  2. Api Server : Node Express
  3. DB : Mongo
  4. Server : Nginx

Root

우선 환경을 구성한다. 기본적으로 3개의 폴더와 docker-compose.yaml 파일로 구성된다.

- client
	- ...sources
	- Dockerfile.dev
- nginx
	- default.conf
	- Dockerfile.dev	
- server
	- ...sources
	- Dockerfile.dev
- docker-compose.yaml	

Client

create-react-app 을 이용해서 구성한다.

- ./client

# create-react-app client

dockerfile.dev 파일을 만들어 준다.

# 노드가 담긴 alpine 이미지 가져오기
FROM node:10.15-alpine

//작업할 디렉토리 설정
WORKDIR "/app"

//npm install을 캐싱하기 위해 package.json만 따로 카피
COPY ./package.json ./
RUN npm install
 
// 소스 복사 
COPY . .

//client 소스 실행
CMD ["npm","run","start"]

Server

express generator를 이용해서 만들 예정이다.

npm install -g express-generator

express server

package.json에는 실시간 개발환경을 위해서 nodemon을 추가해주자.

  "scripts": {
    "dev": "nodemon ./bin/www",
    "start": "node ./bin/www"
  },

서버쪽에도 dockerfile.dev을 만들어보자

FROM node:10.15-alpine

WORKDIR "/app"

COPY ./package.json ./

RUN npm install -g nodemon \
 && npm install
 
COPY . .

CMD ["npm","run","dev"]

Nginx

프록시 서버로 동작될 예정이다.

client와 server 부분을 따로 프록시로 관리한다.

server는 / 로 들어오는 요청을 /api 로 바꿔준다.

파일 두개를 생성해주자

  • default.conf
# nginx/default.conf

upstream client {
    server client:3000;
}

upstream server {
    server server:3050;
}

server {
    listen 80;

    location / {        
        proxy_pass http://client;
    }

    location /sockjs-node {
        proxy_pass http://client;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
    }

    location /api {
        rewrite /api/(.*) /$1 break;
        proxy_pass http://server;
    }
}
  • Dockerfile.dev
# nginx/Dockerfile.dev

FROM nginx
COPY ./default.conf /etc/nginx/conf.d/default.conf

Docker-compose

이제 기본 생성된 파일들을 묶어주고 간편하게 한번에 올릴 수 있게 하자.

  • docker-compose.yaml
version: "3"
services:
  client:                 
    build:
      dockerfile: Dockerfile.dev
      context: ./client    
    volumes:
      - ./client/:/app      
      - /app/node_modules  
    networks:
      - backend           
  server:                          
    build:
      dockerfile: Dockerfile.dev
      context: ./server    
    volumes:
      - ./server/:/app      
      - /app/node_modules      
    environment:
      - NODE_PATH=src
      - PORT=3050
      - DB_HOST=mongo
      - DB=test
      - REDIS_HOST=redis
      - REDIS_PORT=6379      
    networks:
      - backend  
    depends_on:
      - mongo
      - redis 
    ports:
      - "5000:3050"   
  redis:
    container_name: redis
    image: redis
    environment:
      - ALLOW_EMPTY_PASSWORD=yes
    networks:
      - backend 
    volumes:
      - data:/data/redis   
    ports:
      - "6379:6379"
    restart: always    
  mongo:
    container_name: mongo
    image: mongo
    volumes:
      - data:/data/db
    ports:
      - "27017:27017" 
    networks:
      - backend

  nginx:
    restart: always
    build:
      dockerfile: Dockerfile.dev
      context: ./nginx 
    ports:
      - '3000:80' 
    networks:
      - backend
    

networks: 
  backend:
    driver: bridge

volumes:
  data:
    driver: local  

위 내용을 잠깐 설명하자면 기본적으로 서비스는 5개를 실행하고 있다.

  • client
  • server
  • mongo
  • redis
  • nginx

이 많은 서비스를 저 파일 하나로 묶어서 배포를 할수 있다. 멋지다.

자 그럼 실행해서 잘되는 지 체크 해보자.

실행

# --build는 내부 Dockerfile 이 변경시 다시 컴파일 해준다. 
docker-compose up --build or docker-compose up

docker-compose stop or docker-compose down

일단 이상태로는 기본 환경만 올라간 상태라서 뭘 할수 있는 건 아니다.

소스를 좀 더 추가해서 연동을 해보자.

Client

  • App.js
    • npm install axios
import React, { Component } from "react";
import axios from "axios";
import Items from './Items';

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      title: "",
      content: "",
      change: false
    };

    this.handleChange = this.handleChange.bind(this);
    this.sumbit = this.sumbit.bind(this);
  }

  handleChange(event) {
    const target = event.target;
    const value = target.value;
    const name = target.name;

    console.log(name, value);

    this.setState({
      [name]: value
    });
  }

  async sumbit(event){
    event.preventDefault();
    console.log(`${this.state.title},${this.state.content}`);

    await axios
      .post("/api/todos", {
        title: this.state.title,
        content: this.state.content
      })
      .then((result) => {
        this.setState({
          change : true
        });
      })
  }

  render() {
    return (
      <div style={{ margin: "100px" }}>
        <div>
          <form>
            <p>투두리스트</p>
            <input type="text" name="title" onChange={this.handleChange} />
            <br />
            <textarea name="content" onChange={this.handleChange} />
            <button onClick={this.sumbit}>전송</button>
          </form>
        </div>
        <Items change={this.state.change}/>
      </div>
    );
  }
}

export default App;
  
await axios
      .post("/api/todos", {
        title: this.state.title,
        content: this.state.content
      })

여기에서 /api를 통해서 바로 도커내 api서버로 접근을 할 수 있다.

그리고 추가된 리스트는 Items.js 컴포넌트를 만들어서 관리한다.

import React, { Component } from "react";
import axios from "axios";

class Items extends Component {
  constructor(props) {
    super(props);
    this.state = {
        Todos: []
    }
  }

  componentWillReceiveProps(props) {
      console.log(props);
      this.renderTodos();
  }

  componentDidMount() {
      this.renderTodos();
  }

  async renderTodos() {

    try {
        let todos = await axios.get("/api/todos");

        this.setState({
            Todos: todos.data.map(todo => {
                console.log(this.getItem(todo));
                return this.getItem(todo);
            })
        });
    } catch (err) {
        console.log(err);
    }
  }

  getItem(todo) {
    //   console.log(todo);
    return (
      <div key={todo._id}>
        <div style={{ padding: "10px", border:"1px solid red" }}>          
          <div>{todo.title}</div>
          <div>{todo.content}</div>
          <div>{todo.regdate}</div>
        </div>
      </div>
    );
  }

  render() {
    return <div>{this.state.Todos}</div>;
  }
}

export default Items;

결과 화면

image-20190119144313239

아주 심플하지만 작동은 잘 된다. 서버쪽 소스도 업데이트를 해야 잘 나오는 것 명심하자.

Server

Server에서 추가 사항은 몽고 디비와 연동해서 데이터 주고 받는 부분이다.

  • app.js
    • npm install mongoose
  var createError = require('http-errors');
  var express = require('express');
  var path = require('path');
  var cookieParser = require('cookie-parser');
  var logger = require('morgan');
  var cors = require('cors');
  var mongoose = require('mongoose');
  
  var db = mongoose.connection;
  db.on('error', console.error);
  db.once('open', function(){    
      console.log("Connected to mongod server");
  });
  
  mongoose.connect('mongodb://mongo/todo');
  
  var indexRouter = require('./routes/index');
  var usersRouter = require('./routes/users');
  var todoRouter = require('./routes/todo');
  
  var app = express();
  
  //Cors 설정
  app.use(cors());
  
  // view engine setup
  app.set('views', path.join(__dirname, 'views'));
  app.set('view engine', 'jade');
  
  app.use(logger('dev'));
  app.use(express.json());
  app.use(express.urlencoded({ extended: false }));
  app.use(cookieParser());
  app.use(express.static(path.join(__dirname, 'public')));
  
  app.use('/', indexRouter);
  app.use('/users', usersRouter);
  app.use('/todos', todoRouter);
  
  // catch 404 and forward to error handler
  app.use(function(req, res, next) {
    next(createError(404));
  });
  
  // error handler
  app.use(function(err, req, res, next) {
    // set locals, only providing error in development
    res.locals.message = err.message;
    res.locals.error = req.app.get('env') === 'development' ? err : {};
  
    // render the error page
    res.status(err.status || 500);
    res.render('error');
  });
  
  module.exports = app;
  
  • todo.js
var express = require('express');
var router = express.Router();

var Todo = require('../models/todo');

/* GET home page. */
router.get('/', function(req, res, next) {
  return Todo.find({}).sort({regdate: -1}).then((todos) => res.send(todos));
});

router.post('/', function(req, res, next) {
  const todo = Todo();
  const title = req.body.title;
  const content = req.body.content;

  todo.title = title;
  todo.content = content;

  return todo.save().then((todo) => res.send(todo)).catch((error) => res.status(500).send({error}));
});

module.exports = router;
  • model/todo.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var todoSchema = new Schema({
    content: String,
    title: String,
    isdone : Boolean,
    regdate: { type: Date, default: Date.now  }
});

module.exports = mongoose.model('todo', todoSchema);

마무리

다시 docker-compose up --build로 통해서 올려보면 작동이 잘 되는 걸 볼수 있다.

전체 소스는 여기에서 받을수 있다.

이상으로 GDG 부산에서 진행하는 도커 & 쿠버네티스 스터디 내용이었습니다.

참가해주셔서 감사합니다.

Flutter + Steho 사용하기

Flutter 로컬 디비 사용시 대중적인 방법은 sqflite 일것이다.

일반 sqlite 와 문법이 똑같으며 사용하기에도 편하다.

sqlite 를 모바일에서 연동시 페이스북에서 나온 steho 라는 라이버러리가 있다.

안드로이드만 현재 가능하지만 꽤 유용하게 사용가능하다.

그럼 flutter 에서 어떻게 적용하는지 살펴보자.

우선 sqflite 를 import 로 가져오고 생성시 getDatabasePath() 로 가져올 수 있다.

  Future<Database> init() async {
    String documentsDirectory = await getDatabasesPath(); //주의하자.
    final path = join(documentsDirectory, "test.db");
    final db = await openDatabase(
      path,
      version: _version,
      onCreate: _onCreate,
      onUpgrade: (Database db, int oldVersion, int newVersion) async {
        if(newVersion > oldVersion) {
          await File(path).delete();
          _onCreate(db, newVersion);
          print('onUpgrade done');
        }
      }
    );
    return db;
  }

그리고 stetho 라이버러리를 설치를 하자.

마지막으로 main.dart 에서 적용하면 된다.

  if(isInDebugMode) {
    Stetho.initialize();
  }

중요한 점은 디버깅 모드일 경우에만 실행을 해야 한다.

그래서 isInDebugMode 변수를 지정했다.

bool get isInDebugMode {
  bool inDebugMode = false;
  assert(inDebugMode = true);
  return inDebugMode;
}

크롬에서 chrome://inspect/#devices 주소를 열어서 왼쪽 Devices 항목을 클릭시 inspect 항목이 나오는데 그걸 클릭시 새 창이 뜨면서 확인이 가능하다.

Android 만 가능한걸로 안다. 그 점을 유의하자.

 

이상으로 Flutter + Sqflite + Stetho 적용법을 알아보았다.

 

Flutter Codelab을 돌려볼때 오류가 발생될 경우가 있다. 

코드랩 위치 : https://codelabs.developers.google.com/codelabs/mdc-101-flutter


오류 내용은 No toolchains found in the NDK toolchains folder for ABI with prefix: mips64el-linux-android

해결방법은 2가지를 체크 해야 한다. 

1. android 폴더내에 project 레벨에서 build.gradle 에서 dependencies 가 3.1 이상으로 되어있는 지 체크하자. 

dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
}

2. android/gradle/wrapper 폴더내 gradle-wrapper.properties 에 4.6으로 설정

distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip


이 두가지를 설정시 별 문제 없이 빌드 되는 걸 볼수 있다. 

이상으로 해결방법에 대해 공유해본다. 

지치지 않고 제대로 공부하는 7가지 방법 요약

동영상 링크

https://www.youtube.com/watch?v=P2rpSiACoVQ&t=890s


어떻게 해야 우울해 지지 않고 하루에 10시간 이상 공부할 수 있을까? 어떻게 해야 그것에 익숙해질 수 있을까?

이에 대해 쿼라 에서 유명 교수님의 답변을 요약 한 내용입니다.

  1. 나는 당신은 그것을 할수 없다고 생각한다.

  2. 나는 당신이 그것을 시도해서도 안된다고 생각한다.

  3. 아무도 그렇게 많이 공부하지 않는다.

사람이 하루에 3시간이상 열심히 집중하는 건 매우 드물고 바람직하지 않는다

하지만 그래도 꼭 하고 싶다면 다음 7가지 내용을 기억하자.

꾸준하고 제대로 된 공부법은 무엇인가?

1. 하루에 7.5 이상 공부하지 마세요.

당신은 매우 지칠 것이다. 더한다고 해도 도움이 되지 않는다.

당신이 지쳤을 때 당신은 배울수 없다.

2. 일주일에 하루를 쉬세요.

쉬는 날 당신이 좋아하는 것을 쉬세요. 당신의 일은 배움을 위한 것이지 일찍 죽을려고 하는 것이 아니다.

휴식하는 동안에도 머리는 공부하고 있다. 힘들거나 잘 안풀리면 그냥 휴식을 하자.

3. 공부 계획를 세우세요.

각 과목에서 가장 중요한 지식은 무엇인지를 파악하고 그것에 대해 가장 쉽고 효율적으로 공부할 수 있는 공부방법에 대한 계획을 세우도록 하자.

목표를 세우는 이유

  • 무엇을 해야 하는지 알려준다.

  • 동기부여 수단

  • 현재 모습을 구체적으로 보게한다.

4. 낮은 난이도에서 높은 난이도 순으로 공부하세요.

큰틀에서 핵심 아이디어에 익숙해지고 그런 다음 세부 사항에 집중하자.

공부한 다음 피드백으로 그 내용을 다시 요약해보는 습관을 가져보자.

처음부터 디테일 집중하지 말고 크게 보고 작게 넘어가자.

그리고 작은 성공을 많이 이루어 내서 성공의 경험을 많이 맛보자.

기대 수준을 낮춰 작은 성공에 도전하자.

반꼴찌 40등 -> 5등은 어렵다.

하지만 40등 -> 30등은 쉬우므로 성취감도 느끼고 큰틀에서 먼저 공부하는 방식

5. 낮잠을 많이 자자

당신의 기분을 새롭게 하고 당신이 공부 한 것을 기억할 확률을 증가시킨다.

잠자는 것은 메모를 통합시키는 데 도움이 된다.

6. 2.5시간 동안 한 주제를 공부하세요.

그런 다음 다른것으로 전환하자. 이어서 계속 반복하자.

2.5시간동안 한 주제를 반복 공부하자.

7. 읽은 내용 내용을 요약하자.

어렵게 공부하면 잊어지기가 어렵다.

책을 읽고 책을 내려놓고 읽은 것을 요약하자.

요약할 때는 읽은 내용을 보지말고 하자.

동일한 자료를 반복적으로 보지 않고 기억해내는 연습을 해야한다.

자료를 복습하는 건 아무 소용이 없다.

작업의 실제적인 어려움 없이 일하는 것이다. (책을 자꾸 들춰보면서 보는 방법 비추천)

책을 읽을때도 강조를 표시하거나 밑줄을 긋지 말도록 하자. (집중력 저하)

요약 = 요약, 암송, 토론, 발표, 글쓰기등등

공부한 내용을 어떻게든 밖으로 표출하는 것을 추천한다.

힘들지만 계속 하다보면 쉬워진다 = 뇌의 가소성

정리

1,2는 최대 7.5시간 공부하고 휴식하자.

3,4는 계획을 세우고 늦은 난이도에서 높은 난이도로 공부하도록 하자.

5는 낮잠으로 재충전하자.

6는 한 주제 최대 7.5시간

7은 요약하기 (어렵게 공부하면 잊기가 어렵다)

책상에만 앉아 꼼짝않고 책만 보는 건 공부 하수가 하는 일

추천 공부 방법

7.5시간 공부 + 쉬는 시간 확보 +낮잠 + (커피 + 운동)= 공부 효율성 극대화

추천 책 리스트

  1. 완벽한 공부법

  2. 혼자하는 공부법




Tensorflow JS 스터디 4주차

부산에서 매주 진행되는 스터디입니다.

부산에서 다른 스터디 내용을 보실려면 카페 에서 보실수 있습니다.

https://www.udemy.com/machine-learning-with-javascript 을 같이 보면서 공부중입니다.

이전 알고리즘과 비교

  • 어떤 버킷에 공을 놓을때 어떤 위치를 갈지 예측하기 -> Classification

    • Classification 은 예를 들어 해당 메일이 스팸인지 일반 메일인지 판별하는 문제 해결시 사용된다.
  • 주변 시세에 따른 주택 가격 예측 -> Regression (선형 회귀)

주택 가격 예측 (by knn)

1544689793285

KNN 알고리즘

  • 이번 스터디는 텐서플로를 이용해서 KNN 알고리즘 하나하나 구현해본다.

1544690154341

  • 특정 기능과 예측포인트 사이를 찾고
  • 작은 것에서 큰걸로 정렬한다
  • 최상위 K 레코드들을 가져온다.
  • 이러한 최상위 레코드들을 가져온 것에서 평균을 내서 결과값 도출한다.

목표

  • 위도 경도가 주어지면 House Price 가격을 주어진 데이터에 근거해서 뽑아내는 게 목표

준비단계

1544690376477

이전 내용에서는 각각 줄마다 합쳐서 테이블을 만들었다면 이번에는 두개의 features 와 labels 로 나누어서 작업을 할 예정이다.

  • 가짜 데이터들을 먼저 만들도록 한다.

1544690502504

실습

먼저 두개의 별도의 텐서를 만든다. 하나는 위도와 경도를 저장하는 것이고 또 하나는 주택 가격을 저장한다.

const features = tf.tensor([
	[-121, 47],
  [-121.2, 46.5],
  [-122, 46.4],
  [-120.9, 46.7]  
]);
const labels = tf.tensor([
  [200],
  [250],
  [215],
  [240]
]);
const predictionPoint = tf.tensor([-121, 47]);

knn #1 features 와 predictionPoint 와 거리를 구한다.

1544691170114

1544691187998

거리를 구하는 방법을 텐서플로로 진행 해보자.

1544691243831

이 계산식을 하나씩 풀어서 진행해볼 예정이다. 그럼 시작해보자.

Distance Sub

1544691388465

...

features
  .sub(predictionPoint)
[[0 , 0 ], [-0.1999969, -0.5 ], [-1 , -0.5999985], [0.0999985 , -0.2999992]]

제곱

1544691577938

features
  .sub(predictionPoint)
  .pow(2)
[[0 , 0 ], [0.0399988, 0.25 ], [1 , 0.3599982], [0.0099997, 0.0899995]]

Sum

  • 각각의 행에 대한 텐서의 값을을 합산한다.
  • Sum(0 or 1)
    • 0의 경우 메인 축이 세로로 지정된다.
    • 1의 경우 세로측이 메인이다.

1544691705955

features
  .sub(predictionPoint)
  .pow(2)
  .sum(0)
[1.0499985, 0.6999977]

우리가 원하는 가로로 계산하길 원한다.

features
  .sub(predictionPoint)
  .pow(2)
  .sum(0)
[0, 0.2899988, 1.3599982, 0.0999992]

1544692088645

제곱근

1544692117081

features
  .sub(predictionPoint)
  .pow(2)
  .sum(1)
  .pow(0.5)
[0, 0.5385153, 1.1661896, 0.3162265]

정렬

KNN 알고리즘 순서 두번째로 정렬을 진행한다.

  • 텐서들은 정렬할 수 없는 점 유의

1544692330245

  • 거리와 라벨을 따로 떼서 정렬하면 안된다. 아래그림같이 문제가 될 수 있다.

1544692420312

Concat

두개의 텐서들을 합치기 위해 concat 을 이용한다.

1544692526090

1544692556011

features
  .sub(predictionPoint)
	.pow(2)
	.sum(1)
	.pow(0.5)
	.concat(labels)
error : Error in concat1D: rank of tensors[1] must be the same as the rank of the rest (1)

shape 들이 브로드 캐스팅을 하기 위한 조건이 아니어서 발생을 한다.

labels
	.shape

features
  .sub(predictionPoint)
	.pow(2)
	.sum(1)
	.pow(0.5)
	.shape
	
..... 브로드 캐스팅 조건이 맞지 않는다.
[4,1]
[4]

ExpandDims

  • 부족한 줄을 채워 주기 위해 expandDims() 를 적용한다.
features
  .sub(predictionPoint)
	.pow(2)
	.sum(1)
	.pow(0.5)
	.expandDims()

...
[[0, 0.5385153, 1.1661896, 0.3162265],]

1544692996609

한칸 더 생긴걸 볼수 있다.

features
  .sub(predictionPoint)
	.pow(2)
	.sum(1)
	.pow(0.5)
	.expandDims()
	.shape
...
[1,4]

아직도 label 쪽과 맞지 않는다.

  • Axis 를 1로 해서 바꿔준다.
...
.expandDims(1)
	
...
[4,1]

이제 다시 concat 을 해보자

features
  .sub(predictionPoint)
	.pow(2)
	.sum(1)
	.pow(0.5)
	.expandDims(1)
	.concat(labels)
	
....
[[0 ], [0.5385153], [1.1661896], [0.3162265], [200 ], [250 ], [215 ], [240 ]]

하지만 결과를 보면 [feature,label] 순으로 합쳐 있지 않다.

  • concat 시 축을 1로 바꿔서 해보자

1544693281622

features
  .sub(predictionPoint)
	.pow(2)
	.sum(1)
	.pow(0.5)
	.expandDims(1)
	.concat(labels, 1)
	
....
[[0 , 200], [0.5385153, 250], [1.1661896, 215], [0.3162265, 240]]

이제 정상적으로 합쳐진걸 볼수 있다.

하지만 아직 정렬에서 문제가 있다.

1544693511557

현재 이런 구조인데 이 상태로 정렬이 안된다는 것이다. 이걸 해결하기 위해서 unstack 이라는 함수로 각각의 텐서로 쪼개어서 정렬할 수 있다.

unstack

1544693583648

features
  .sub(predictionPoint)
	.pow(2)
	.sum(1)
	.pow(0.5)
	.expandDims(1)
	.concat(labels, 1)
	.unstack()

1544693730949

각각의 텐서들로 쪼개어진 걸 볼수 있다.

만약 특정 위치의 값을 뽑기 위해선 배열형태로 접근이 가능하다.

...
.unstack()[3]
...
[0.3162265, 240]

정렬의 문제

일반 오브젝트 형태를 정렬시 동작이 안된다. 아래는 잠깐 예를 들어본다

const distance = [
  { value: 10 },
  { value: 30 },
  { value: 20 }
];

distance.sort()
........
[{"value":10},{"value":30},{"value":20}]
  • sort 함수를 좀 더 확장해서 정렬을 할 수 있다.
const distance = [
  { value: 10 },
  { value: 30 },
  { value: 20 }
];

distance.sort((a,b) => {
	return a.value > b.value ? 1 : -1
})

.......
[{"value":10},{"value":20},{"value":30}]

그럼 이어서 unstack 한 부분에 적용을 해보자.

features
  .sub(predictionPoint)
	.pow(2)
	.sum(1)
	.pow(0.5)
	.expandDims(1)
	.concat(labels, 1)
	.unstack()
	.sort((a, b) => {
		a.get(0) > b.get(0) ? 1 : -1
	})

최상위 K 레코드 가져오기

정렬된 배열에서 최상위 오브젝트들을 가져오기 위해 slice 을 사용한다.

const k = 2;

features
  .sub(predictionPoint)
	.pow(2)
	.sum(1)
	.pow(0.5)
	.expandDims(1)
	.concat(labels, 1)
	.unstack()
	.sort((a, b) => {
		a.get(0) > b.get(0) ? 1 : -1
	})
	.slice(0,k)
...

[{"isDisposedInternal":false,"shape":[2],"dtype":"float32","size":2,"strides":[],"dataId":{},"id":7895,"rankType":"1"},
{"isDisposedInternal":false,"shape":[2],"dtype":"float32","size":2,"strides":[],"dataId":{},"id":7897,"rankType":"1"}]

평균 구하기

합계 ( reduce ) / count

const distance = [
  { value: 10 },
  { value: 30 },
  { value: 20 }
];

distance.reduce((acc,obj) => {
	return acc + obj.value
}, 0)

60 / 3(distance length)
.......
60 -> 20(결과)

위의 예제를 토대로 다시 본 소스에 적용해보자.

const k = 2;

features
  .sub(predictionPoint)
	.pow(2)
	.sum(1)
	.pow(0.5)
	.expandDims(1)
	.concat(labels, 1)
	.unstack()
	.sort((a, b) => {
		a.get(0) > b.get(0) ? 1 : -1
	})
	.slice(0,k)
	.reduce((acc, pair) => acc + pair.get(1), 0) / k;
	
...	
225

그럼 labels 기준으로 했을때 가장 근접한 수치가 225 로 뽑을수 있다.

여기까지가 텐서플로을 이용해서 KNN 알고리즘을 적용하는 방법이었다.

그럼 이제 다음 내용에는 실제 데이터를 가지고 작업을 해보자.

소스 준비

git clone https://github.com/StephenGrider/MLKits.git
git checkout 84f617c4549409c104aa994465101d1a8fd164e2

npm install

1544706669880

Index.js

이제 위에서 공부했던 내용을 코드로 옮겨보자.

기본적으로 모듈은 두개를 설치를 한다.

require('@tensorflow/tfjs-node');
// 만약 gpu 를 사용시 tfjs-node-gpu 적용
const tf = require('@tensorflow/tfjs');

load csv file

이미 준비된 데이터를 이용한다.

require('@tensorflow/tfjs-node');
const tf = require('@tensorflow/tfjs');
const loadCSV = require('./load-csv');

let { features, labels, testFeatures, testLabels } = loadCSV('kc_house_data.csv', {
    // 무작위로 섞기
    shuffle: true,
    // 테스트셋 지정 -> testfeatures, testlabels
    splitTest: 10,
    // features 지정 (위도, 경도) -> features
    dataColumns: ['lat', 'long'],
    // price 지정 -> label
    labelColumns: ['price'],    
});

console.log(testFeatures);
console.log(testLabels);
[ [ 47.561, -122.226 ],
  [ 47.6595, -122.186 ],
  [ 47.5081, -122.093 ],
  [ 47.5276, -122.161 ],
  [ 47.6695, -122.333 ],
  [ 47.6769, -122.36 ],
  [ 47.5442, -122.141 ],
  [ 47.699, -122.206 ],
  [ 47.3196, -122.399 ],
  [ 47.2843, -122.357 ] ]
[ [ 1085000 ],
  [ 466800 ],
  [ 425000 ],
  [ 565000 ],
  [ 759000 ],
  [ 512031 ],
  [ 768000 ],
  [ 1532500 ],
  [ 204950 ],
  [ 247000 ] ]

Tensorflow JS 작성

function knn(features, labels, predictionPoint, k) {
    return features
        .sub(predictionPoint)
        .pow(2)
        .sum(1)
        .pow(0.5)
        .expandDims(1)
        .concat(labels, 1)
        .unstack()
        .sort((a, b) => {
            a.get(0) > b.get(0) ? 1 : -1
        })
        .slice(0, k)
        .reduce((acc, pair) => acc + pair.get(1), 0) / k;
}

knn 함수 적용

  • 변수들을 텐서로 변환해서 사용해야 한다
features = tf.tensor(features);
labels = tf.tensor(labels);
testFeatures = tf.tensor(testFeatures);
testLabels = tf.tensor(testLabels);
  • 하나의 값만 우선 테스팅 하기 위해 testFeatures[0] 으로 가져온다.
const result = knn(features, labels, tf.tensor(testFeatures[0]), 10);
console.log('Guess', result, testLabels[0][0]);

.....
Guess 559100 1085000

하지만 559100 1085000 두개의 값이 가격 차이가 너무 나는 걸 볼 수 있다.

이걸 다시 개선하는 방법에 대해서 공부해보자.

정확도

  • 우선 에러가 작을수록 좋다. 이걸 측정해보자.

1544708102786

위의 공식을 코드로 구현하면 다음과 같다.

const err = (testLabels[0][0] - result) / testLabels[0][0];
console.log('Err', err * 100);

.......
Err 48.47004608294931

약 48프로 정도 에러가 발생하고 있다.

그럼 전체 testFeatures 에 대해 불정확률을 구해보면

testFeatures.forEach((testPoint, i) => {
    const result = knn(features, labels, tf.tensor(testPoint), 10);
    const err = (testLabels[i][0] - result) / testLabels[i][0];
    console.log('Err', err * 100);
});

....
Err 48.47004608294931
Err -19.77292202227935
Err -31.55294117647059
Err 1.0442477876106195
Err 26.337285902503293
Err -9.192607478844055
Err 27.200520833333336
Err 63.51712887438825
Err -172.79824347401805
Err -126.35627530364373

이러한 내용에 좀 더 정확도를 올리기 위해 좀 더 추가적인 features 를 추가해본다.

  • sqft_lot : 평방 피트
let { features, labels, testFeatures, testLabels } = loadCSV('kc_house_data.csv', {
    // 무작위로 섞기
    shuffle: true,
    // 테스트셋 지정 -> testfeatures, testlabels
    splitTest: 10,
    // features 지정 (위도, 경도) -> features
    dataColumns: ['lat', 'long', 'sqft_lot'],
    // price 지정 -> label
    labelColumns: ['price'],
});

표준화

처음에 배웠던 내용중에 normalize 하는 방법에 대해서 공부를 했었다.

1544708829630

표준화 하는 과정

1544708882008

이 방법을 지금 내용에 도입해보자.

  • 중간에 너무 범위가 커서 제대로된 표준화가 힘들어 보이는 문제점이 있다

1544709183947

이 부분을 해결하기 위해서 새로운 함수를 적용시켜 보자.

  • StandartDeviation : 표준편차

1544709363419

moments

tensorflowjs 에는 함수를 제공 한다. => moments

예시를 들어보자.

const numbers = tf.tensor([
	[1, 2],
  [3, 4],
  [5, 6]
]);

tf.moments(numbers);

....
{"mean":{"isDisposedInternal":false,"shape":[],"dtype":"float32","size":1,"strides":[],"dataId":{},"id":41,"rankType":"0"},"variance":{"isDisposedInternal":false,"shape":[],"dtype":"float32","size":1,"strides":[],"dataId":{},"id":50,"rankType":"0"}}

우리는 여기에서 mean 과 variance 를 사용할 수 있다.

  • average : mean

  • value : numbers

  • StandartDeviation : sqrt(variance)

1544709860334

const numbers = tf.tensor([
  [1, 2],
  [3, 4],
  [5, 6]
]);

// 0은 축을 바꿔서 계산한다.
const { mean, variance} = tf.moments(numbers, 0);

numbers.sub(mean).div(variance.pow(.5))

.........
[[-1.2247449, -1.2247449], [0 , 0 ], [1.2247449 , 1.2247449 ]]

이제 본 소스에 적용해보자.

function knn(features, labels, predictionPoint, k) {
    const { mean, variance } = tf.moments(features, 0);

    const scaledPrediction = predictionPoint.sub(mean).div(variance.pow(0.5))

    return features
        .sub(mean)
        .div(variance.pow(0.5))
        .sub(scaledPrediction)
        .pow(2)
        .sum(1)
        .pow(0.5)
        .expandDims(1)
        .concat(labels, 1)
        .unstack()
        .sort((a, b) => {
            a.get(0) > b.get(0) ? 1 : -1
        })
        .slice(0, k)
        .reduce((acc, pair) => acc + pair.get(1), 0) / k;
}
Error -15.323502304147466
Error -11.344580119965723
Error -2.047058823529412
Error 19.327433628318584
Error 7.806324110671936
Error -14.106372465729613
Error -8.782552083333334
Error 13.227406199021207
Error -36.336911441815076
Error 7.381578947368421

30프로정도 확률안에서 도는 정도까지 수정했다.

이 보다 정확도를 올리는 방법은 두가지가 있다.

  • testFeatures 수를 좀 더 수를 늘리거나
  • 훈련수를 늘리거나

이건 다음 알고리즘 수업을 통해서 공부를 해보자.

전체 소스

require('@tensorflow/tfjs-node');
const tf = require('@tensorflow/tfjs');
const loadCSV = require('./load-csv');

function knn(features, labels, predictionPoint, k) {
  const { mean, variance } = tf.moments(features, 0);

  const scaledPrediction = predictionPoint.sub(mean).div(variance.pow(0.5));

  return (
    features
      .sub(mean)
      .div(variance.pow(0.5))
      .sub(scaledPrediction)
      .pow(2)
      .sum(1)
      .pow(0.5)
      .expandDims(1)
      .concat(labels, 1)
      .unstack()
      .sort((a, b) => (a.get(0) > b.get(0) ? 1 : -1))
      .slice(0, k)
      .reduce((acc, pair) => acc + pair.get(1), 0) / k
  );
}

let { features, labels, testFeatures, testLabels } = loadCSV(
  'kc_house_data.csv',
  {
    shuffle: true,
    splitTest: 10,
    dataColumns: ['lat', 'long', 'sqft_lot', 'sqft_living'],
    labelColumns: ['price']
  }
);

features = tf.tensor(features);
labels = tf.tensor(labels);

testFeatures.forEach((testPoint, i) => {
  const result = knn(features, labels, tf.tensor(testPoint), 10);
  const err = (testLabels[i][0] - result) / testLabels[i][0];
  console.log('Error', err * 100);
});


'스터디 > Tensorflow JS' 카테고리의 다른 글

tensorflow js 입문 (스터디 3주차 정리)  (0) 2018.12.05

NodeJS 스터디 1주차 정리

부산에서 매주 진행되는 노드 입문 강좌 스터디입니다.

부산에서 다른 스터디 내용을 보실려면 카페 에서 보실수 있습니다.

이 강좌는 제로초님의 인프런 강좌를 보고 요약해놓은 내용입니다.

  • 챕터 2 - ES2018
  • 챕터 3 - 노드 기능 알아보기

const & let

const

const 는 객체가 할당된 경우 변경이 불가능하지만 오브젝트에 대한 내부 값은 변경가능하다.

const item = {a: 1, b: 2, c: 3};

item.a = 3;

item => {a: 3, b: 2, c: 3}

let

구문적인 변수 영역 규칙을 지원한다.

{} 블록내에 변수 영역만 처리된다.

예를 들어 보자.

var 로 할 경우 밖의 변수까지 변경되는 걸 볼수 있다.

var test = "test";

if (test) {
    var test = "test2";
    console.log('블록내 : ',test);
}

console.log('블록밖', test);

.....
블록내 : test2
블록밖 test2

하지만 let 으로 할 경우 글로벌 변수의 값을 보호할 수 있다.

var test = "test";

if (test) {
    let test = "test2";
    console.log('블록내 : ',test);
}

console.log('블록밖 : ', test);

......
블록내 :  test2
블록밖 :  test

템플릿 문자열

템플릿 문자열은 문자열 연결 대신 사용할 수 있다. 중간에 변수를 삽입할수 있다.

${변수}, ` 로 감싼다.

console.log(test + " " + test2); //기존 방식

console.log(`${test} ${test2}); //템플릿 문자열

객체 리터럴

화살표 함수 (중요)

화살표를 사용하면 모든 함수 정의를 한줄로 끝낼수 있다. function 키워드를 없앴고, 화살표가 어떤 값을 반환하는지 지정해주기 때문에 return 도 없다. 만약 파라미터가 하나만 받는 경우 괄호도 생략이 가능하다.

화살표 함수는 this를 새로 바인딩 하지 않는다. 예를 들어 다음 코드에서 this는 city객체가 아니다.

const city = {
    areas: ["서울","부산"],
    print: function(delay = 1000) {
        setTimeout(function() {
            console.log(this.areas.join(','))
        }, delay);
    }
}

city.print();

....error
Cannot read property 'join' of undefined

이 경우 this는 window 객체이기 때문에 city 는 undefined 이다.

화살표 함수는 함수 내부의 this를 외부의 this와 같게 만든다.

const city = {
    areas: ["서울","부산"],
    print: (delay = 1000) => {
        setTimeout( () => {
            console.log(this.areas.join(','))
        }, delay);
    }
}

city.print();

....error
Cannot read property 'join' of undefined

위 코드에서 this도 결국에는 window 의 객체이기 때문에 오류가 난다.

그래서 상단의 print 함수를 function 으로 만들어주면 오류가 발생되지 않는다.

const city = {
    areas: ["서울","부산"],
    print: function(delay = 1000){
        setTimeout( () => {
            console.log(this.areas.join(','))
        }, delay);
    }
}

city.print();

.....error
서울,부산

구조분해 ( destructuring )

구조분해를 사용하면 객체 안에 있는 필드 값을 원하는 변수에 대입할 수 있다.

비구조화 할당 시 this가 의도와 다르게 동작하는 현상이 있을수 있는 점 유의하자.

var city = {
    area: ['busan','seoul'],
    latlng: ['123:456', '789:321']
}

const { area, latlng } = city;

console.log(area[0], latlng[0]);
...

const [ busan, seoul ] = area;

console.log( busan, seoul );

위 코드 처럼 배열도 비구조화 할당이 가능하다.

스프레드 연산자

기존 배열의 값은 변경하지 않고 새로 만들어서 리턴한다.

파라미터 갯수를 동적으로 받을수 있다.

const x = (a, ...b) => console.log(a,b);

x(1,2,3,4,5);

........
1 [ 2, 3, 4, 5 ]

Promise

비동기적인 동작을 잘 다루기 위한 방법 중 하나이다.

const promise = new Promise((resolve, reject) => {
    const a = 1;
    const b = 1;
    if(a + b <= 2) {
        resolve(a + b);
    } else {
        reject(a + b);
    }
});

promise
    .then(( success ) => {console.log(success)})
    .catch((error) => {console.log(error)});


.... 성공
> 2

또한 여러개의 then으로 사용가능하다. 콜백안에 다시 콜백이 있을 경우 여러개의 then 으로 이어진다. 그러기 위해선 resolve로 리턴을 해야줘야 한다.

const promise = new Promise((resolve, reject) => {
    const a = 1;
    const b = 1;
    if(a + b <= 2) {
        resolve(a + b);
    } else {
        reject(a + b);
    }
});

promise
    .then(( success ) => {
        return new Promise((resolve, reject) => {
            resolve("result : " + success);
        })
    })
    .then((success2) => console.log(success2))
    .catch((error) => {console.log("reject:" + error)});

....
result : 2

Promise API

무조건 성공 또는 실패 하는 경우일 경우 더 줄여서 가능하다.

const success = Promise.resolve('success');    
const failed = Promise.reject('failed');

Promise 함수들을 모아서 하나의 Promise 로 실행할수 있다.

전부 성공시 success 로 넘어가지만 한개라도 실패시 catch 로 들어가는 점 유의하자.

Promise.all([promise, success])
    .then((results) => {
        console.log(results);
    })
    .catch((error) => {
        console.log(error);
    })

Async / Await

Async 도 결국 내부에는 Promise 기반이므로 Promise를 꼭 습득하고 가자.

Promise의 단점은 결국 콜백함수내에서 이루어지기 때문에 외부 코드들과 연동성이 비동기로 이루어진다. 그래서 그걸 극복하게 동기형으로 바꿔주는 게 Async/Await 이다.

check = async () => {
    console.log('await result: ',await promise);
}

check();

오류 처리는 try / catch 문으로 한다.

check = async () => {
    try {
        console.log('await result: ',await promise);
    } catch(e) {
        console.error('error: ',e)
    }
}

check();

모듈

자바스크립트 모듈은 자바스크립트 파일에서 쉽게 불러와서 활용할 수 있는 재사용 가능한 코드 조각들을 말한다. 즉 쪼개서 불러와서 사용이 가능하다는 것이다.

자바스크립트는 각각의 모듈을 별도의 파일로 저장한다. 모듈을 만들고 외부에 익스포트 하는 방법에는 한 모듈에서 여러 자바스크립트 객체를 외부에 노출시키는 방법과 모듈당 하나의 자바스크립트 객체를 노출시키는 방법이 있다.

여러 객체를 외부에 노출 시키는 방법

>> helper.js

export const print = (message) => log(message, new Date())

export const log = (message, timestamp) => console.log(`${timestamp.toString()}: ${message}`)

module.exports = {
    print,
    log
}
const { print } = require('./txt_helper');

print("good");

하나의 객체를 외부에 노출 시키는 방법

const print = (message) => console.log(message, new Date())

export default print;
import print from './txt_helper_only_one.mjs';

print("good");

global

노드의 전역 객체

노드 실행환경내 전역에서 공통적으로 사용되는 객체이다.

> B.js

module.exports = () => global.message;
> C.js

const B = require('./B');

global.message = 'ok';

console.log(B());

>> ok 출력

console

console 객체네는 디버깅을 도와주는 많은 메소드가 준비되어 있다.

  • console.time('인자') & console.timeEnd('인자') - 전체 걸리는 시간을 측정해준다. (인자는 똑같아야 한다)
  • dir: 오브젝트 객체를 로깅할때 사용한다. colors 는 색상 지정, depth 는 깊이 지정
  • trace: 에러 발생시 오류 경로를 추척해서 보여준다.

setTimeout, setInterval, setImmediate

https://nodejs.org/ko/docs/guides/timers-in-node/

  • setTimeout : 일정시간 후에 콜백이 실행된다.
    • clearTimeout 로 취소 가능
  • setInterval : 일정 시간마다 콜백이 실행된다.
    • clearInterval로 취소 가능
  • setImmediate : 이벤트 루프 주기 끝에 코드를 실행한다.
    • clearImmediate로 취소 가능

__filename, __dirname

  • __filename 은 현재 파일 위치까지 위치를 리턴한다.
  • __dirname 은 디렉토리까지 위치를 리턴한다.

process

현재 실행중인 노드 프로그램 정보가 들어있다.

  • process.version : 노드 설치 버전
  • process.arch : 32비트인지 64비트인지 체크
  • process.platform : 운영체제
  • process.pid : 프로세스 아이디 ( 문제가 발생시 강제 종료시 아이디 지정 가능 )
  • process.uptime : 프로세스 실행되고 얼마나 지났는지 체크
  • process.cwd : 노드 프로그램이 실행되고 있는 위치
  • process.exePath : 노드 설치 위치
  • process.cpuUsage : cpu 사용량
  • process.exit : 프로세스 종료

OS

운영체제 관련된 모듈

  • os.cpu 의 경우는 멀티 프로세싱에서 자주 쓰인다.

Path

경로 관련된 모듈. **중요한 모듈중 하나이다. **

  • path.delimiter : 환경변수 패스 출력
  • path.sep : 경로 구분자 표시 . 윈도우는 \\ 이다.
  • path.dirname : 경로 폴더
  • path.extname : 확장자
  • path.basename : 파일 이름
  • path.parse : 구조로 분해
  • path.format : 분해된 구조를 합쳐준다.
  • path.normalize : 잘못된 경로를 자동으로 정상적으로 만들어준다.
  • path.isAbsoulte : 경로가 절대경로인지 상대경로인지 체크
  • path.join : 절대 경로 무시하고 조각나있는 경로를 합쳐준다.
  • path.resolve : 절대 경로 고려해서 합친다. 루트는 C:\

Url

도메인이 생략된 경우는 parse 를 통해서 처리

  • parse
  • format
  • searchParams : 파라미터 내용을 오브젝트 형태로 가져온다.
    • 파라미터들을 추가,수정,삭제등 할수 있다. (append,set,delete,get)

url 구조� �� �미� ��결과

querystring

  • url 모듈과 같이 사용한다.
  • url.parse ->querystring.parse 로 이용
  • stringify 는 파싱된 걸 다시 합쳐준다.

crypto

단방향 암호화(해시)
  • 비밀번호를 암호화할때 필요하다.

  • 복호화는 안된다.

  • createHash 는 비밀번호가 짧을 수록 같은 값이 나올수 있다.

  • 좀 더 나은 암호화 방식인 pbkdf2 함수를 사용

    • salt 라는 문자열을 비밀번호에 추가해서 좀 더 암호화한다.
    • salt 는 암호화된 비밀번호와 같이 저장
    • iteration 은 1초정도가 걸릴때까지 올려주면 좋음
  • 실무에선 bcrypt 등이 사용된다.

crypto.createHash('sha512').update('password').digest('base64');
양방향 암호화 (복호화 가능)
  • 암호화시 createCipher 함수 사용
  • aes-256 또는 aes-256-cbc 를 많이 사용한다.
  • 복호화시 createDecipher 함수 사용
  • key 값을 알아야 복호화를 할 수 있다.
  • update 함수 사용시 base64,utf8 인자가 암호화와 복호화시 바뀐다.

Util

  • callbackify : promise 함수를 거꾸로 callback 함수로 바꿔준다.
  • promisifycallback 함수를 promise 로 바꿔주는 유틸 함수 (중요함)
  • deprecate : 폐기되는 함수임을 지정한다.
    • 오류 메세지도 인자로 넣을 수 있다.
    • 결과에 Warning 이 나온다.

Fs (File System)

  • 비동기 방식으로 콜백으로 결과로 받는다.
  • 여러개의 콜백을 지정시 순서가 일정하지가 않다.
const fs = require('fs');
fs.readFile('./readme.txt', (err, data) => {
    if(err) {
        throw err;
    }

    console.log(data); //바이너리 파일로 리턴된다. 
    console.log(data.toString());
});

.........
<Buffer ed 85 8c ec 8a a4 ed 8a b8 31 0d 0a ed 85 8c ec 8a a4 ed 8a b8 32>
테스트1
테스트2
  • 동기 방식으로는 sync 방식도 제공한다.
    • readFileSync

기타 함수

  • access
  • mkdir
  • open
  • rename
  • unlink : 파일 삭제
  • rmdir : 폴더 삭제

promise

  • 노드 10에서 promise사용가능

버퍼 ( Buffer ), 스트림 ( Stream )

  • 데이터를 보낼때 일정량의 저장 공간에(버퍼) 담아서 보낸다.
  • 버퍼들의 통로를 뜻함
const fs = require('fs');
const readStream = fs.createReadStream('./readme.txt', { highWaterMark: 8});
const data = [];

readStream.on('data', (chunk) => {
    data.push(chunk);
    console.log('data', chunk, chunk.length);
});

readStream.on('end', () => {
    console.log('end', Buffer.concat(data).toString());
});

readStream.on('error', (error) => {
    console.log('error', error);
})
data <Buffer ed 85 8c ec 8a a4 ed 8a> 8
data <Buffer b8 31 ed 85 8c ec 8a a4> 8
data <Buffer ed 8a b8 32 ed 85 8c ec> 8
data <Buffer 8a a4 ed 8a b8 31 ed 85> 8
data <Buffer 8c ec 8a a4 ed 8a b8 32> 8
data <Buffer ed 85 8c ec 8a a4 ed 8a> 8
data <Buffer b8 31 ed 85 8c ec 8a a4> 8
data <Buffer ed 8a b8 32 ed 85 8c ec> 8
data <Buffer 8a a4 ed 8a b8 31 ed 85> 8
data <Buffer 8c ec 8a a4 ed 8a b8 32> 8
data <Buffer ed 85 8c ec 8a a4 ed 8a> 8
data <Buffer b8 31 ed 85 8c ec 8a a4> 8
data <Buffer ed 8a b8 32 ed 85 8c ec> 8
data <Buffer 8a a4 ed 8a b8 31 ed 85> 8
data <Buffer 8c ec 8a a4 ed 8a b8 32> 8
data <Buffer ed 85 8c ec 8a a4 ed 8a> 8
data <Buffer b8 31 ed 85 8c ec 8a a4> 8
data <Buffer ed 8a b8 32 ed 85 8c ec> 8
data <Buffer 8a a4 ed 8a b8 31 ed 85> 8
data <Buffer 8c ec 8a a4 ed 8a b8 32> 8
data <Buffer ed 85 8c ec 8a a4 ed 8a> 8
data <Buffer b8 31 ed 85 8c ec 8a a4> 8
data <Buffer ed 8a b8 32 ed 85 8c ec> 8
data <Buffer 8a a4 ed 8a b8 31 ed 85> 8
data <Buffer 8c ec 8a a4 ed 8a b8 32> 8

청크별로 날라와서 end 이벤트 등록시 그 버퍼를 합쳐서 하나의 완전품으로 만든다.

WriteStream

const fs = require('fs');

const writeStream = fs.createWriteStream('./readme.txt');
writeStream.on('end', () => {
    console.log('완료')
})
writeStream.write('쓰기 완료1');
writeStream.write('두번째 줄');
writeStream.end();

Pipe

내부적으로 읽기와 쓰기를 연결해준다.

  • copy 함수가 따로 새로 만들어져서 사용가능하다.

zlib

압축을 할수 있다.

Event

미리 이벤트를 만들어 놓고 어떤 행동 발생시 콜백이 발생한다.

  • addListener
  • on : addListener 와 같지만 여러번 등록가능하다.
  • once : 한번만 실행된다.
  • listenerCount : 이벤트가 몇개 달려 있는지 갯수 확인
const EventEmitter = require('events');
const myEvent = new EventEmitter();

myEvent.addListener('visit', () => {
    console.log('welcome');    
});

myEvent.on('exit', () => {
    console.log('good bye');
});

myEvent.on('exit', () => {
    console.log('good bye please');
});

myEvent.once('special', () => {
    console.log('special event')
});

myEvent.emit('visit');
myEvent.emit('exit');
myEvent.emit('special');
myEvent.emit('special');

..........
welcome
good bye
good bye please
special event <-- 한번만 실행된걸 볼 수 있다. 

이벤트 취소

  • removeAllListeners(이름) 여러번 등록 했을 경우 다 제거
  • removeListener(이름, callback) 하나만 제거 가능하다. 어떤 걸 제거 해야 할지 알수 없으므로 콜백을 지정

예외처리

  • 노드는 싱글 쓰레드이므로 오류 발생시 전체에 영향을 끼칠수 있으므로 신중하게 처리
  • throw new Error
  • try / catch 로 처리 가능
  • async / await 도 try / catch
  • 내장 함수들은 error 발생해도 시스템이 중지되진 않는다.
  • process.on 으로 시스템 에러가 발생시 중지가 되지 않게 할 수 있다.
    • uncaughtExcepetion 으로 모든 에러를 잡을수 있다.
    • process.on 이벤트 실행이 꼭 보장되지 않는다.


이상으로 1주차 노드 스터디 내용 정리사항이었습니다. 

참석해주셔서 감사합니다. 

Tensorflow JS 입문 ( 부산 3주차 스터디 )

부산에서 매주 진행되는 스터디입니다.

부산에서 다른 스터디 내용을 보실려면 카페 에서 보실수 있습니다.

https://www.udemy.com/machine-learning-with-javascript 을 같이 보면서 공부중입니다.

목표

  • 텐서플로 JS 에 대한 이해
  • 텐서플로를 이용한 예제 재설계 ( lodash -> tensorflow)
  • tensorflow 를 이용해서 knn 알고리즘 재설계
  • tensorflow와 함계 다른 알고리즘 빌딩

저번주까지(2주차) 우리는 기본적인 머신러닝 기법인 KNN 에 대해서 공부했다.

공부한 내용은 다음과 같다.

1543958445494

공식 홈페이지

https://js.tensorflow.org/

1543959044497

첫번째 예제

tensorflow js를 이용해서 배열에 있는 숫자들을 작업하자

1543959200980

Dimensions

  • 1차원, 2차원, 3차원...n차원 배열

1543959388735

Shape

  • 각각의 배열에 얼마나 많은 레코드를 가지고 있는지 표현
  • 배열에 대한 길이를 뜻함

예를 들어 [5,10,17] 에 대한 shape 는 3 이라고 생각하면 된다.

1543959771631

1543959631283

1543959690992

1543959728153

실습

https://stephengrider.github.io/JSPlaygrounds/

const data = tf.tensor([1,2,3])

data.shape

>> [3]
const data = tf.tensor([1,2,3])
const otherdata = tf.tensor([4,5,6]);

data.add(otherdata);

>> [5,7,9]

1543960433839

const data = tf.tensor([1,2,3])
const otherdata = tf.tensor([4,5,6]);

data.sub(otherdata);
data.mul(otherdata);


>> [-3, -3, -3]
>> [4, 10, 18]
const data = tf.tensor([
  [1,2,3],
  [4,5,6]
]);
const otherdata = tf.tensor([
  [4,5,6],
  [1,2,3]
]);

data.add(otherdata);

>> [[5, 7, 9], [5, 7, 9]]

1543960846449

행렬의 길이가 안맞는 경우

지금까지 같은 배열을 계산을 진행했다. 하지만 만약 길이가 다른 경우에는 어떻게 처리할까?

1543960682971

const data = tf.tensor([1,2,3]);
const otherdata = tf.tensor([4]);

data.add(otherdata);

>> [5,6,7]

브로드캐스팅

행렬의 길이가 다른 경우에도 동작을 하기 위해선 조건이 있다. 그에 대해서 알아보자.

  • 오른쪽에서 왼쪽으로 이동되며 shapes 길이가 똑같거나 어느 한쪽이 1이 되어야 한다.

1543961094501

어느 한쪽길이가 1이라서 아래 두개의 수식은 성립된다. by 브로드캐스팅

1543961191494

오른쪽부터 왼쪽으로 1이 있으므로 계산 가능하다.

1543961320616

다른 텐서들중 하나가 없더라도 다른게 일치한다면 계산이 가능하다.

1543961420823

1543961530424

텐서플로 핸들링

Get

const data = tf.tensor([10, 20, 30]);

data.get(0);

>> 10

2차원 배열일 경우에는

1543961845376

const data = tf.tensor([
  [10, 20, 30],
  [40, 50, 60],  
]);

data.get(0, 1);

>> 20

값을 가져올때 중요한 점은 (row, column) 으로 가져온다는 것이다.

Set

const data = tf.tensor([
  [10, 20, 30],
  [40, 50, 60],  
]);

data.set(0, 0, 50) //작동안됨

Slice

const data = tf.tensor([
  [10, 20, 30],
  [40, 50, 60],
  [10, 60, 30],
  [10, 70, 30],
  [10, 80, 30],
  [10, 90, 30],
  [10, 100, 30],
  [10, 110, 30],
]);

data.slice([0,1], [8, 1])

>> [[20 ], [50 ], [60 ], [70 ], [80 ], [90 ], [100], [110]]

1543962708670

size 는 시작위치가 1이다.

....

data.slice([0,1], [-1, 2]) // 전체 길이를 선택시 -1로 가능하다. 

Concat

const tensorA = tf.tensor([
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9],
]);

const tensorB = tf.tensor([
  [10, 11, 12],
  [13, 14, 15],
  [16, 17, 18],
]);

tensorA.concat(tensorB);


>> [[1 , 2 , 3 ], [4 , 5 , 6 ], [7 , 8 , 9 ], [10, 11, 12], [13, 14, 15], [16, 17, 18]]

이 결과에서 만약 shape를 구하면 어떻게 될까?

tensorA.concat(tensorB).shape

>> [6,3]

???? 어떻게 이런 결과가 나올까?

처음 단계를 살펴보면 A,B 를 합치는 경우

1543963264350

이런 형태를 추측 할 수 있다.

하지만 shape 결과로 나온걸 추측해보면

1543963362289

이렇게 구성된 걸 볼 수 있다.

왜 이렇게 되는 건지 살펴보자.

Axis

텐서플로를 축으로 shape 계산이 가능하다.

기본 세로축은 0, 가로축은 1 로 계산한다.

1543963457162

가로축 기준으로 생각해보면

1543963528907

const tensorA = tf.tensor([
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9],
]);

const tensorB = tf.tensor([
  [10, 11, 12],
  [13, 14, 15],
  [16, 17, 18],
]);

tensorA.concat(tensorB,0).shape
tensorA.concat(tensorB,1).shape

>> [6,3]
>> [3,6]

실제 예제

만약 멀리뛰기를 예를 들어보자.

1543963712867

const jumpData = tf.tensor([
  [70, 70, 70],
  [80, 70, 90],
  [70, 70, 70]
]);

const playerData = tf.tensor([
  [1,160],
  [2,160],
  [3,160],
  [4,160],
]);

jumpData.sum(0)
jumpData.sum(1)

>> 
[220, 210, 230] //세로축 기준
[210, 240, 210] //가로축 기준

두개의 shape 가 안맞아서 에러가 난다.

const jumpData = tf.tensor([
  [70, 70, 70],
  [80, 70, 90],
  [70, 70, 70]
]);

const playerData = tf.tensor([
  [1,160],
  [2,160],
  [3,160],
  [4,160],
]);

jumpData.concat(playerData)

>> 에러

Error: Error in concat2D: Shape of tensors[1] (4,2) does not match the shape of the rest (3,3) along the non-concatenated axis 1.

이럴 경우 해결 방법에 대해서 알아보자.

const jumpData = tf.tensor([
  [70, 70, 70],
  [80, 70, 90],
  [70, 70, 70],
  [70, 70, 70]
]);

const playerData = tf.tensor([
  [1,160],
  [2,160],
  [3,160],
  [4,160],
]);

jumpData.sum(1, true).concat(playerData, 1)

>> [[210, 1, 160], [240, 2, 160], [210, 3, 160], [210, 4, 160]]

expandDims

const jumpData = tf.tensor([
  [70, 70, 70],
  [80, 70, 90],
  [70, 70, 70],
  [70, 70, 70]
]);

const playerData = tf.tensor([
  [1,160],
  [2,160],
  [3,160],
  [4,160],
]);

jumpData.sum(1).expandDims(1)

>> [[210], [240], [210], [210]]
const jumpData = tf.tensor([
  [70, 70, 70],
  [80, 70, 90],
  [70, 70, 70],
  [70, 70, 70]
]);

const playerData = tf.tensor([
  [1,160],
  [2,160],
  [3,160],
  [4,160],
]);

jumpData.sum(1).expandDims(1).concat(playerData, 1)

>> [[210, 1, 160], [240, 2, 160], [210, 3, 160], [210, 4, 160]]

여기까지 3주차 스터디 내용이었다.

주로 배운 내용은 텐서플로 JS 에서의 이해였다.

참석해주셔서 감사합니다.

'스터디 > Tensorflow JS' 카테고리의 다른 글

Tensorflow JS 스터디 4주차  (0) 2018.12.13

No toolchains found in the NDK toolchains folder for ABI with prefix: mips64el-linux-android


원인 : 오래된 flutter 플젝을 열때 나오는 버그이다. 

Open android/gradle/gradle-wrapper.properties and change this line:

distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip

to this line:

distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip

Open android/build.gradle and change this line:

classpath 'com.android.tools.build:gradle:3.0.1'

to this:

classpath 'com.android.tools.build:gradle:3.1.2'


NodeJS Advance 2주차 스터디

부산에서 매주 진행되는 NodeJS Advance 스터디입니다.

더 많은 스터디는 네이버 카페에서 확인 부탁드립니다.

스터디 내용은 Udemy 강좌를 같이 보고 정리를 한 글입니다.

1주차 스터디 내용

이번 스터디에서는 노드의 성능향상에 대해서 공부하도록 한다.

  • Cluster 모드를 사용
  • Worker Threads를 사용

우선 테스트를 위해 express 를 사용해보자.

const express = require('express');
const app = express();

app.get('/', (req, res) => {
    res.send('Hi there');
});

app.listen(3000);

위와 같이 구성될 경우 일반적이라고 볼수 있는데 이건 그림으로 표현하자면

1543129280118

의도적 딜레이

만약 요청을 5초정도로 하는 경우 동시에 실행시 어떻게 되는지 체크 해보자.

const express = require('express');
const app = express();

function doWork(duration) {
    const start = Date.now();
    while(Date.now() - start < duration) {

    }
}

app.get('/', (req, res) => {
    doWork(5000);
    res.send('Hi there');
});

app.listen(3000);

하나만 실행했을 경우 5초가 걸릴것이다.

만약 2개를 동시에 실행시 처음 5초가 끝나고 두번째가 실행된다.

  • 2번째를 동시 실행시 8.57초가 걸리는 걸 볼수 있다.

1543129512873

이를 해결하기 위해 Cluster 라는 방법을 사용할 수 있다.

1543129640802

Cluster 동작원리

  • 처음 index.js 를 호출시 cluster manager를 통해서 cluster.fork() 를 실행
  • 그 후 다시 index.js 로 가서 worker instance를 실행한다.

1543129735477

소스로 구현해보자.

  • cluster.fork()를 통해서 다시 index.js 를 실행해서 프로세스를 새로 만든다.
const cluster = require('cluster');

if(cluster.isMaster) {
    cluster.fork();
} else {
    const express = require('express');
    const app = express();

    function doWork(duration) {
        const start = Date.now();
        while(Date.now() - start < duration) {

        }
    }

    app.get('/', (req, res) => {
        doWork(5000);
        res.send('Hi there');
    });

    app.listen(3000);

}

다중 클러스터

만약 Cluster 를 여러개 설정시 어떻게 될까?

cluster.fork();
cluster.fork();
cluster.fork();

테스트시 다른 탭에 있는 2개의 쓰레드가 빨리 도는 걸 확인할수 있다는데...잘 안된다...;;

AB 테스트

맥이나 리눅스에서 AB 테스팅할 수 있는 방법이 있다고 한다.

50번의 요청을 동시에 50번을 날리도록 실행

ab -c 50 -n 50 localhost:3000/fast

1543131006116

문제점

클러스터를 실행하더라도 한정된 자원이 있기 때문에 점점 가성비가 떨어진다고 한다.

이번 시간에는 클러스트 개념과 기본 사용법에 대해서 살펴보았다.

참석해주셔서 감사합니다.

도커&쿠버네티스 7주차 스터디 정리내용

부산에서 매주 진행되는 스터디입니다.

부산에서 다른 스터디 내용을 보실려면 카페 에서 보실수 있습니다.

https://www.udemy.com/docker-and-kubernetes-the-complete-guide 을 공부하고 있습니다

1주차 스터디

2주차 스터디

3주차 스터디

4주차 스터디

5주차 스터디

6주차 스터디

이번주 스터디 내용

이번주는 저번 시간에 이어서 Dockerhub로 올린 이미지를 aws에 beanstalk로 올리는 걸 진행하도록 하겠다.

4주차에는 바로 도커이미지를 aws beanstalk 로 올리는 걸 진행했었다.

이제는 실제 Production에 사용되는 형태로 진행하는 걸 배워볼 것이다.

여기서 진행된 소스는 여기 Github 에서 받을수 있다.

배우게 되는 내용

  • AWS EC2 Task Definition
  • AWS VPS 등 기본 설정

1542771228782

AWS EB 설정

aws eb에 여러개의 도커 이미지(client, serverworker)등을 올리기 위해서는 설정 파일이 필요하다.

  • Dockerrun.aws.json

1542764486465

1542764529514

AWS ECS Task Definition

Beanstalk 에는 어떻게 컨테이너들이 동작될지를 알수가 없다. 그래서 aws ec2에서는 task definition 기능을 통해서 이미지를 어떻게 동작될지를 정의를 할 수 있다.

1542764875545

실습

/Dockerrun.aws.json

Dockerhub 에 올라간 tag/name 을 확인하자.

  • name : 이미지 이름

  • image : 도커 허브에 올라간 이미지 이름

  • hostname : docker-compose.yml 에 있는 이름

  • essential : 만약 콘테이너가 어떤 이유로 문제가 생겼을 경우 연관되어 있는 다른 컨테이너들을 같이 내릴건지 여부 ( true 이면 다같이 내린다. )

  • memory : 각 컨터이너마다 얼마나 메모리를 할당할건지 지정

  • portMapping : 외부 포트와 내부 콘테이너 포트를 바인딩

  • links : 콘테이너가 연관되어 있는 걸 연결한다.

    1542765885341

{
    "AWSEBDockerrunVersion" : 2,
    "containerDefinitions" : [
        {
            "name": "client",
            "image": "bear2u/multi-client",
            "hostname": "client",
            "essential": false,
            "memory": 128
        },
        {
            "name": "server",
            "image": "bear2u/multi-server",
            "hostname": "api",
            "essential": false,
            "memory": 128
        },
        {
            "name": "worker",
            "image": "bear2u/multi-worker",
            "hostname": "worker",
            "essential": false,
            "memory": 128
        },
        {
            "name": "nginx",
            "image": "bear2u/multi-nginx",
            "hostname": "nginx",
            "essential": true,
            "portMappings": [
                {
                    "hostPort": 80,
                    "containerPort": 80
                }
            ],
            "links": ["client", "server"],
            "memory": 128
        }
    ]
}

Aws Beanstalk

AWS 콘솔에 들어가서 새로운 어플리케이션을 만들어준다.

  • multi-docker

1542766325151

create one > Multi-container Docker 선택

1542766420194

1542766452949

1542766606837

Prod. 구조

Dockerrun.aws.json 내부에는 따로 postgres 와 redis 에 관한 DB 콘테이너가 없다. 그러면 어떻게 설정할까?

기존에 배웠던 개발 구조

1542766660010

배우게 될 배포(Production) 구조



위 그림에서 보다시피 Redis 는 AWS Elastic cache 로 빠져있고 postgres 는 aws rds 로 빼서 사용하고 있는 걸 볼수 있다. 가능하다면 이런 구조를 만드는게 더 이점이 많다고 한다.

이렇게 빼는 이유에 대해서 알아보자.

AWS Elastic Cache (Redis)

  • 자동으로 redis 인스턴스를 만들어주고 유지보수 해준다.
  • 스케일링하기 쉽다.
  • 로깅 및 유지관리
  • 보안성이 우리가 하는것보다 전문가들이 하기 때문에 더 안전하다.

AWS RDS (Postgres)

  • 자동으로 postgres 인스턴스를 만들수 있고 유지보수 하기도 쉽다.

  • 스케일링 하기 쉽다.

  • 로깅 및 유지보수하기 쉽다.

  • 우리가 하는것보다 안전하다.

  • 자동으로 백업 및 롤백 지원

다음에는 내부 콘테이너에 넣어서 하는 방법에 대해서도 공부할 예정이다.

EB와 DB 연결

1542767378510

현재로써는 EB에서는 RDS와 EC 연결을 서로 알수 없는 구조이다. 이걸 연결하기 위해서는 aws 콘솔에서 서로 묶어주는 게 필요하다.

1542767495818

1542767712822

VPC

VPC 에 가보면 MultiDocker-env 라는 security group 이 만들어진 걸 볼 수 있다.

1542767849145

EB 와 DB 간의 연결

1542767924722

Postgres 생성

  1. RDS (postgres`) 를 만든다.

하단에 프리티어 체크 박스를 꼭 활성화를 해주자.

1542768159192

  1. Postgress 기본 입력
  • identifier: multi-docker-postgress
  • username : postgres
  • password : postgresspassword

1542768322944

1542768448718

1542768508822

Elastic chache (Redis) 생성

1542768613773

Node type 값을 t2로 선택을 하는 걸 꼭 유의하자.

1542768704289

1542769330034

1542769364030

Create Security Group

1542769789607

1542769829241

inbound 설정

1542769905067

각 서비스 Security Group 지정

  • 방금 만든 security 설정을 elasticache와 rds 에 지정해준다.

REDIS VPC 지정

1542770208533

RDS 수정

1542770335728

1542770365368

1542770404982

Elastic Beanstalk 수정