기본 콘텐츠로 건너뛰기

[Kubernetes - Operator] KUDO를 활용한 Galera Operator 단계별로 적용하기 - PART 3: Bootstrap 삭제 및 서비스 중단 없이 Scale Up/Down 처리

How to building real world sample step by step - Part 3

게시글 참고

이 게시글은 KUDO Blog 샘플을 기준으로 테스트하면서 발생한 문제점들을 처리하고 동작을 검증하면서 정리한 내용입니다.

  • PART 1에서는 부트스트랩 노드 구성
  • PART 2에서는 클러스터에 노드들이 참여할 떄 사용할 서비스와 설정등을 구성하고 StatefulSet을 구성

이 문서는 KUDO Blog 샘플을 기준으로 테스트하면서 발생한 문제점들을 해결하고 동작을 검증하면서 정리한 내용으로 구성된 Galera Cluster의 사용하지 않는 bootstrap 정보를 제거하고, 외부 연결을 위한 서비스 생성 및 서비스의 중단없이 Scale Up/Down 할 수 있도록 나머지 부분을 적용해 본다. 이 과정까지 완료되면 프로덕션 환경에 적용할 수 있는 정도가 된다.

Cleanup bootstrap node

PART 2 에서 모든 노드들을 클러스터에 참여시켰기 때문에 더 이상은 부트스트랩 노드가 필요하지 않다.

따라서 operator.yaml에 부트스트랩 노드와 관련된 리소스를 제거하는 Step과 Task를 추가하도록 한다.

...
plans: 
  deploy:
    strategy: serial
    phases:
      - name: deploy
        strategy: serial
        steps:
          ...
          - name: cleanup    # 추가
            tasks:
              - bootstrap_cleanup
tasks:
  ...
  - name: bootstrap_cleanup      # 추가
    kind: Delete
    spec:
      resources:
        - bootstrap_deploy.yaml
        - bootstrap_service.yaml
        - bootstrap_config.yaml

추가된 bootstrap_cleanup은 삭제 작업으로 PART 1에서 만들었던 bootstrap 관련 리소스들이 대상이며 이 작업이 실행되면 resources로 지정된 *.yaml 파일에 정의된 부트스트랩 리소스들 (ConfigMap, Service, Deploy)이 모두 삭제된다.

그러나 실제 Galera 노드들에서 사용하는 ConfigMap에 부트스트랩 노드 정보 (wsrep_cluster_address = gcomm://{{ .Name }}-bootstrap-svc)가 남아 있기 떄문에 Galera 인스턴스가 누락된 노드를 중심으로 작동하므로 반드시 문제가 되는 것은 아닐지라도 완전성을 위해 관련된 정보도 삭제해 주는 것이 좋다.

따라서 opreator.yaml파일에 ConfigMap을 수정할 수있는 Task를 추가하고 Step에서 이 Task를 이용하는 것으로 아래와 같이 변경한다.

...
plans: 
  deploy:
    strategy: serial
    phases:
      - name: deploy
        strategy: serial
        steps:
          ...
          - name: cleanup
            tasks:
              - bootstrap_cleanup
              - config      # 추가
tasks:
  ...
  - name: bootstrap_cleanup
    kind: Delete
    spec:
      resources:
        - bootstrap_deploy.yaml
        - bootstrap_service.yaml
        - bootstrap_config.yaml
  - name: config      # 추가
    kind: Apply
    spec:
      resources:
        - galera_config.yaml

이번에는 하나의 Step에서 두 개의 Task가 동작하도록 정의했다. deploy phase의 실행 전략을 strategy: serial로 지정했으므로 두 개의 Task는 순차적으로 처리된다.

참고

여기서 주의할 것은 PART 2에서 사용했던 galera_config.yaml을 다시 사용한다는 것이다. 즉, 노드를 설치할 때와 삭제할 때 설정 기능이 다른데 하나의 리소스를 재 사용하는 상황인 것이다. 이를 해결하기 위해서 템플릿에서 .StepName 변수를 활용해서 어떤 Step에서 호출되었는지를 식별해서 처리하는 방식을 사용한다.

StepName 정보를 기준으로 내용을 구성할 수 있도록 템플릿 리소스인 templates/galera_config.yaml 파일을 아래와 같이 수정한다.

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Name }}-nodeconfig
  namespace: {{ .Namespace }}
data:
  galera.cnf: |
    [galera]
    wsrep_on = ON
    wsrep_provider = /usr/lib/galera/libgalera_smm.so
    wsrep_sst_method = mariabackup
    {{ if eq .StepName "firstboot_config" -}}
    wsrep_cluster_address = gcomm://{{ .Name }}-bootstrap-svc,{{ $.Name }}-{{ $.OperatorName }}-0.{{ $.Name }}-hs{{ range $i, $v := untilStep 1 (int .Params.NODE_COUNT) 1 }},{{ $.Name }}-{{ $.OperatorName }}-{{ $v }}.{{ $.Name }}-hs{{end}}
    {{ else -}}
    wsrep_cluster_address = gcomm://{{ $.Name }}-{{ $.OperatorName }}-0.{{ $.Name }}-hs{{ range $i, $v := untilStep 1 (int .Params.NODE_COUNT) 1 }},{{ $.Name }}-{{ $.OperatorName }}-{{ $v }}.{{ $.Name }}-hs{{end}}
    {{ end -}}
    wsrep_sst_auth = "{{ .Params.SST_USER }}:{{ .Params.SST_PASSWORD }}"
    binlog_format = ROW
  innodb.cnf: |
    [innodb]
    innodb_autoinc_lock_mode = 2
    innodb_flush_log_at_trx_commit = 0
    innodb_buffer_pool_size = 122M

wsrep_cluster_address 정보를 지정할 때 템플릿에 조건부 처리를 지정했다. 즉, firstboot_config Step일 경우는 부트스트랩 구성이 적용되고, 그 외는 클러스터에 참여할 노드 정보들만 구성되는 것이다.

{{ if eq .StepName "firstboot_config" -}}
  wsrep_cluster_address = gcomm://{{ .Name }}-bootstrap-svc,{{ $.Name }}-{{ $.OperatorName }}-0.{{ $.Name }}-hs{{ range $i, $v := untilStep 1 (int .Params.NODE_COUNT) 1 }},{{ $.Name }}-{{ $.OperatorName }}-{{ $v }}.{{ $.Name }}-hs{{end}}
{{ else -}}
  wsrep_cluster_address = gcomm://{{ $.Name }}-galera-0.{{ $.Name }}-hs{{ range $i, $v := untilStep 1 (int .Params.NODE_COUNT) 1 }},{{ $.Name }}-galera-{{ $v }}.{{ $.Name }}-hs{{end}}
{{ end -}}

if, else, end 구문은 -}} 으로 종료시키고 있다. 이는 템플릿 엔진에게 해당 라인을 빈줄로 뇌두지 말고 삭제하도록 지정하는 것이다.

위와 같이 템플릿 내에 조건문을 사용해서 다른 여러 Task들에서 재 사용할 수 있다.

이제 Pod가 재 시작되면 post-bootstrap cluster에 대한 올바른 구성을 가지고 다시 백업을 시작할 수 있게 된다.

지금까지 작업한 것을 기준으로 정상적으로 동작하는지 테스트를 하도록 한다.

  • KUDO 설치 (using init)

    $ kubectl kudo init --unsafe-self-signed-webhook-ca --wait  # Webhook TLS를 자체 서명한 버전으로 처리
  • Galera Operator 설치 (using install)

    $ kubectl kudo install ./
  • 설치된 Operator Instance 확인

    $ kubectl kudo plan status --instance=galera-operator-instance
  • 설치된 ConfigMap 및 Pod 확인

    $ kubectl describe configmap galera-operator-instance-nodeconfig
    

    Name: galera-operator-instance-nodeconfig Namespace: default Labels: heritage=kudo kudo.dev/instance=galera-operator-instance kudo.dev/operator=galera-operator Annotations: kudo.dev/last-applied-configuration: {“kind”:“ConfigMap”,“apiVersion”:“v1”,“metadata”:{“name”:“galera-operator-instance-nodeconfig”,“namespace”:“default”,“creationTimestamp”:n… kudo.dev/last-plan-execution-uid: 8cd36d4b-aeb5-473e-8997-1fad6090722a kudo.dev/phase: deploy kudo.dev/plan: deploy kudo.dev/step: cleanup

    Data

    galera.cnf:

    [galera] wsrep_on = ON wsrep_provider = /usr/lib/galera/libgalera_smm.so wsrep_sst_method = mariabackup wsrep_cluster_address = gcomm://galera-operator-instance-galera-0.galera-operator-instance-hs,galera-operator-instance-galera-1.galera-operator-instance-hs,galera-operator-instance-galera-2.galera-operator-instance-hs wsrep_sst_auth = “root:admin” binlog_format = ROW

    innodb.cnf:

    [innodb] innodb_autoinc_lock_mode = 2 innodb_flush_log_at_trx_commit = 0 innodb_buffer_pool_size = 122M

    Events:

    정상적으로 ConfigMap이 조건에 따라서 wsrep_cluster_address 설정 값이 부트스트랩 정보없이 노드들의 정보만으로 구성된 것을 확인할 수 있다.

    $ kubectl get pods
    

    NAME READY STATUS RESTARTS AGE galera-operator-instance-galera-operator-0 1/1 Running 0 3m28s galera-operator-instance-galera-operator-1 1/1 Running 0 3m3s galera-operator-instance-galera-operator-2 1/1 Running 0 2m35s nfs-client-provisioner-b84668c6d-zxt8d 1/1 Running 0 91m

    부트스트랩 노드가 제거되고 나머지 노드들만 존재하는 것을 확인할 수 있다.

  • KUDO 및 Operator 삭제

    $ kubectl kudo init --unsafe-self-signed-webhook-ca --upgrade --dry-run --output yaml | kubectl delete -f -

Add service for connect from client

이제 클라이언트가 Galera Cluster에 연결할 수 있는 방법이 필요하다. Galera Cluster는 다중 마스터 구조이기 때문에 어디로 연결되더라도 안전하게 읽고 쓸 수 있다. 서비스는 어떤 의존성도 갖지않기 때문에 deploy plan의 어떤 단계에서 생성하던지 상관이 없지만 이미 클러스터 내부의 서비스를 생성한 단계가 있으므로 여기에 추가하도록 한다.

operator.yaml 파일의 이미 존재하는 cluster_service Task에 아래와 같이 추가한다.

...
  - name: cluster_services
    kind: Apply
    spec:
      resources:
        - hs-service.yaml
        - cs-service.yaml   # 추가
...

이제 사용한 서비스 리소스인 templates/cs-service.yaml 파일을 생성하고 아래와 같이 구성한다.

apiVersion: v1
kind: Service
metadata:
  name: {{ .Name }}-cs
  namespace: {{ .Namespace }}
  labels:
    app: galera
    galera: {{ .Name }} 
spec:
  ports:
    - port: {{ .Params.MYSQL_PORT }}
      name: mysql
  selector:
    app: galera
    instance: {{ .Name }}

이 서비스는 mySQL 포트만 필요하고 클라이언트가 Galera Clustr에 연결할 수 있도록 load balancing된 ClusterIP를 제공한다.

지금까지 작업한 것을 기준으로 정상적으로 동작하는지 테스트를 하도록 한다.

  • KUDO 설치 (using init)

    $ kubectl kudo init --unsafe-self-signed-webhook-ca --wait  # Webhook TLS를 자체 서명한 버전으로 처리
  • Galera Operator 설치 (using install)

    $ kubectl kudo install ./
  • 설치된 Operator Instance 확인

    $ kubectl kudo plan status --instance=galera-operator-instance
  • 설치된 Service 확인

    $ kubectl get services
    

    NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE galera-operator-instance-cs ClusterIP 10.104.170.201 3306/TCP 5m3s galera-operator-instance-hs ClusterIP None 3306/TCP,4444/TCP,4567/TCP,4568/TCP 5m3s

    실행 중인 클러스터에 모든 서비스가 생성된 것을 확인할 수 있다. 서비스의 상세 정보를 통해서 정상적으로 생성되었는지 확인한다.

    $ kubectl describe service galera-operator-instance-cs
    

    Name: galera-operator-instance-cs Namespace: default Labels: app=galera galera=galera-operator-instance heritage=kudo kudo.dev/instance=galera-operator-instance kudo.dev/operator=galera-operator Annotations: kudo.dev/last-applied-configuration: {“kind”:“Service”,“apiVersion”:“v1”,“metadata”:{“name”:“galera-operator-instance-cs”,“namespace”:“default”,“creationTimestamp”:null,"label… kudo.dev/last-plan-execution-uid: f5b71f8e-b9ef-49b0-81d1-8f82315e63d4 kudo.dev/phase: deploy kudo.dev/plan: deploy kudo.dev/step: cluster_services Selector: app=galera,instance=galera-operator-instance Type: ClusterIP IP: 10.104.170.201 Port: mysql 3306/TCP TargetPort: 3306/TCP Endpoints: 10.244.247.12:3306,10.244.84.132:3306 Session Affinity: None Events: Type Reason Age From Message


    Warning FailedToUpdateEndpointSlices 4m37s endpoint-slice-controller Error updating Endpoint Slices for Service default/galera-operator-instance-cs: failed to update galera-operator-instance-cs-brmb9 EndpointSlice for Service default/galera-operator-instance-cs: Operation cannot be fulfilled on endpointslices.discovery.k8s.io “galera-operator-instance-cs-brmb9”: the object has been modified; please apply your changes to the latest version and try again

    • 클라이언트 연결 확인

    생성한 서비스를 통해서 클라이언트가 Galera Cluster에 접속할 수 있다.

    $ kubectl run mysql-client --image=mysql:5.7 -it --rm --restart=Never -- mysql -u root -h galera-operator-instance-cs -p

    진행 중에 아래와 같은 메시지가 보이면 params.yaml에 정의했던 password를 입력하면 된다.

    If you don't see a command prompt, try pressing enter.

    mysql> 프롬프트가 보이면 정상적으로 연결이 된 것이다.

  • KUDO 및 Operator 삭제

    $ kubectl kudo init --unsafe-self-signed-webhook-ca --upgrade --dry-run --output yaml | kubectl delete -f -

Scale up/down without service interruption

KUDO는 파라미터의 값을 변경함으로써 연계된 Plan 이 구동된다. 파라미터로 정의한 NODE_COUNT를 변경하면 deploy plan이 구동될 것이기 때문에 ConfigMap을 변경하고 클러스터에 새로운 노드 수를 반영하기 위해서 StatefulSet이 재 시작될 필요가 있다.

이런 작업을 반영하기 위해 operator.yaml 파일에 새로운 plan을 아래와 같이 추가한다.

...
plans: 
  deploy:
    ...
  node_update:      # 추가
    strategy: serial
    phases:
      - name: deploy
        stratege: serial
        steps:
          - name: config
            tasks:
              - config
          - name: stateful
            tasks:
              - statefulset
...

이 Plan은 기존의 Task를 재 사용하는 것이기 때문에 별도의 Task 추가는 필요하지 않고 두 개의 Step이 순차적으로 실행되도록 정의한 것이다.

이제 NODE_COUNT 파라미터가 변경되면 위에 정의한 새로운 Plan이 구동될 수 있도록 params.yaml의 파라미터 정보를 아래와 같이 추가한다.

apiVersion: kudo.dev/v1beta1
parameters:
  ...
  - name: NODE_COUNT
    description: "Number of nodes to create in the cluster"
    default: "3"
    trigger: node_update      # 추가
  ...

PART 2 에서는 trigger 정보가 없었으며 이런 경우는 기본 값으로 deploy plan이 호출되게 된다. trigger를 지정해서 node_update plan을 호출하도록 한 것이다.

지금까지 작업한 것을 기준으로 정상적으로 동작하는지 테스트를 하도록 한다.

  • KUDO 설치 (using init)

    $ kubectl kudo init --unsafe-self-signed-webhook-ca --wait  # Webhook TLS를 자체 서명한 버전으로 처리
  • Galera Operator 설치 (using install)

    $ kubectl kudo install ./
  • 설치된 Operator Instance 확인

    $ kubectl kudo plan status --instance=galera-operator-instance
  • NODE_COUNT 변경 검증

    $ kubectl kudo update --instance galera-operator-instance -p NODE_COUNT=3

    파라미터를 통한 변경이 적용되었기 때문에 변경에 따른 처리를 확인한다.

    $ kubectl kudo plan status --instance=galera-operator-instance

    Pod가 재 시작되는 시간이 있기 때문에 IN_PROGRESS 상태로 나타나며, 이 작업이 진행 중인 동안에 kubectl get pods 명령을 수행하면 추가된 수 만큼 노드가 배포가 되고, 이전에 동작하던 노드들은 한번에 하나씩 재 시작되는 것을 볼 수 있다. 반대로 NODE_COUNT 파라미터의 수를 줄이면 동작하던 노드들이 제거되는 것을 확인할 수 있다.

  • KUDO 및 Operator 삭제

    $ kubectl kudo init --unsafe-self-signed-webhook-ca --upgrade --dry-run --output yaml | kubectl delete -f -

Process syncronization before shuting down nodes

Scale up은 상관이 없지만 down의 경우는 노드가 제거될 때 Galera Cluster의 노드가 동기화되지 않으면 클러스터의 상태에 문제가 발생할 수 있으므로 노드가 종료되기 전에 동기화를 해야할 필요가 있다.

이를 위해서 상태를 검증하는 스크립트를 추가하고 StatefulSet의 spec에 preStop check를 수정해서 처리를 수행하도록 operator.yaml 파일의 deploy plan의 Statefulset Step 이전에 적용될 수 있도록 Step과 Task를 추가한다.

...
plans:
  deploy:
    strategy: serial
    phases:
      - name: deploy
        strategy: serial
        steps:
          ...
          - name: node_scripts    # 추가
            tasks:
              - node_scripts
          ...
          - name: statefulset
          ...
  ...
tasks:
  ...
  - name: node_scripts    # 추가
    kind: Apply
    spec:
      resources:
        - node_scripts.yaml

상태를 검증하는 스크립트를 위한 ConfigMap 리소스인 templates/node_scripts.yaml 파일을 생성하고 아래와 같이 구성한다.

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Name }}-node-scripts
  namespace: {{ .Namespace }}
  wait-for-sync.sh: |
    until mysql -u root -p{{ .Params.MYSQL_ROOT_PASSWORD }} -e "SHOW GLOBAL STATUS LIKE 'wsrep_local_state_comment';" | grep -q Synced 
    do
        echo "Waiting for sync"
        sleep 5
    done

그리고 이 스크립트가 StatefulSet에 정상적으로 마운트될 수 있도록 templates/statefulset.yaml에 추가한다.

        volumeMounts:
        - name: {{ .Name }}-config
          mountPath: /etc/mysql/conf.d
        - name: {{ .Name }}-datadir
          mountPath: /var/lib/mysql
        - name: {{ .Name }}-node-scripts    # 추가
          mountPath: /etc/galera/wait-for-sync.sh
          subPath: wait-for-sync.sh
      volumes:
      - name: {{ .Name }}-config
        configMap:
          name: {{ .Name }}-nodeconfig
          items:
            - key: galera.cnf
              path: galera.cnf
            - key: innodb.cnf
              path: innodb.cnf
      - name: {{ .Name }}-node-scripts    # 추가
        configMap:
          name: {{ .Name }}-node-scripts
          defaultMode: 0755

ConfigMap에서 subPath로 스크립트을 직접 마운트했다. subPath를 사용할 때 제한들이 있지만 여기서는 큰 의미가 없다.

또한 Kubernetes 클러스터를 구성하는 노드들의 장애로 부터 보호하기 위해서 분리된 노드들로 배포되도록 스케줄되어 있는지에 대한 확인이 필요하다. 이 작업은 AntiAffinity (반 선호도) 규칙을 사용해서 처리가 가능하며 테스트를 위해서 이 규칙을 비활성화 시킬 수 있다. templates/statefulset.yaml 파일에 아래와 같이 조건 섹션을 구성한다.

...
spec:
  {{ if eq .Params.ANTI_AFFINITY "true" }}
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchExpressions:
              - key: "app"
                operator: In
                values:
                - galera
              - key: "instance"
                operator: In
                values:
                - {{ .Name }}
          topologyKey: "kubernetes.io/hostname"
  {{ end }}
...

참고

상기 예제의 Anti-Affinity는 반 선호도에 대한 Pod Selector 개념으로 topologyKey를 kubernetes.io/hostname으로 설정해서 Pod가 존재하는 동일한 Host가 아닌 다른 곳으로 배치하나는 의미가 된다.

사용된 ANTI_AFFINITY 파라미터를 params.yaml 파일에 추가한다.

...
  - name: ANTI_AFFINITY
    description: "Enforce pod anit-affinity"
    default: False
...

지금까지의 작업으로 Operator는 Node 선호도를 관리하고 안전하게 Scale up/down이 가능하고, 외부의 클라이언트가 접근할 수 있도록 구성되었다.

이 시점에서 몇 가지 벤치마크와 클러스터를 확장하고 축소하는 동시에 모든 것이 정상적으로 작동하는지를 확인해야 한다. how to measure mysql performance in kubernetes with sysbench를 이용해서 확인이 가능하다.

Conclusion

지금까지 Galera Cluster를 구성하고 운영하는 Operator를 단계별로 확인해 보았으며, 그 과정을 통해서 KUDO에서 제공하는 내장 변수들과 Sprig 템플릿 함수를 이용해서 조건부 템플릿을 적용해 리소스 파일들을 재 사용할 수 있다는 것도 확인했다.

향후에는 시간이 허락하고 관련된 작업을 진행하게 된다면 지금까지 확인한 설치관련 (Day 1 Operation) 뿐만 아니라 운영 중 (Day 2 Operation) 에 발생할 수 있는 상황들에 대한 부분을 추가로 검토해 볼 예정이다.

참고 자료

댓글

이 블로그의 인기 게시물

OData 에 대해서 알아보자.

얼마 전에 어떤 회사에 인터뷰를 하러 간 적이 있었다. 당시 그 회사는 자체 솔루션을 개발할 기술인력을 찾고 있었고 내부적으로 OData를 사용한다고 했다. 좀 창피한 이야기일 수도 있지만 나름 기술적인 부분에서는 많은 정보를 가지고 있다고 했던 것이 무색하게 OData란 단어를 그 회사 사장님에게서 처음 들었다. 작고, 단순한 사이트들만을 계속해서 작업을 하다 보니 어느덧 큰 줄기들을 잃어버린 것을 느끼기 시작했다. 명색이 개발이 좋고, 기술적인 기반을 만들려고 하는 인간이 단어조차도 모른다는 것은 있을 수 없는 것이라서 다시 새로운 단어들과 개념들을 알아보는 시간을 가지려고 한다. OData (Open Data Protocol) 란? 간단히 정리하면 웹 상에서 손쉽게 데이터를 조회하거나 수정할 수 있도록 주고 받는 웹(프로토콜)을 말한다. 서비스 제공자 입장에서는 웹으로 데이터를 제공하는 방식으로 각 포탈 사이트들이 제공하는 OPEN API 포맷을 독자적인 형식이 아니라 오픈된 공통규약으로 제공 가능하며, 개발자는 이 정보를 다양한 언어의 클라이언트 라이브러리로 어플리케이션에서 소비할 수 있도록 사용하면 된다. 공식 사이트는 www.odata.org 이며 많은 언어들을 지원하고 있다. 좀더 상세하게 정의를 해 보면 OData는 Atom Publishing Protocol  (RFC4287) 의 확장 형식이고 REST (REpresentational State Transfer) Protocol 이다. 따라서 웹 브라우저에서 OData 서비스로 노출된 데이터를 볼 수 있다. 그리고 AtomPub 의 확장이라고 했듯이 데이터의 조회만으로 한정되는 것이 아니라 CRUD 작업이 모두 가능하다. Example 웹 브라우저에서 http://services.odata.org/website/odata.svc 를 열어 보도록 하자. This XML file does not appear to have any style in...

C# 에서 Timer 사용할 때 주의할 점.

예전에 알고 지내시던 분의 질문을 받았다. Windows Forms 개발을 하는데, 주기적 (대략 1분)으로 데이터 요청을 하는 프로그램을 작성하기 위해서 Timer 를 사용하는데, 어떤 기능을 처리해야 하기 때문에 Sleep 을 같이 사용했다고 한다. 여기서 발생하는 문제는 Sleep 5초를 주었더니, Timer 까지 5초 동안 멈춘다는 것이다. Timer 라는 것은 기본적으로 시간의 흐름을 측정하는 기능이기 때문에 Sleep 을 했다고 해서 Timer 가 멈추는 일은 생겨서는 안된다. 그러나 실제 샘플을 만들어 보면 ... Timer 가 Sleep 만큼 동작이 멈추는 것을 확인할 수 있다. Windows Forms 는 UI Thread 를 사용하는 것으로 최적화 되어 있으며 여기서 Timer 를 쓰면 UI Thread 에 최적화된 System.Windows.Forms.Timer 가 사용된다. 여기서 문제의 발생이 시작되는 것이다. Sleep 을 사용하게 되면 UI Thread 가 Sleep 이 걸리기 때문에 여기에 속한 Timer 까지도 멈추는 것이다. 이런 문제를 해결하기 위해서는 System.Threading.Timer 를 사용해야 한다. 이 Timer 는 별도의 Thread 에서 동작하기 때문에 Sleep 의 영향을 받지 않는다. 언뜻 보면 쉬운 해결 방법인 것 같지만 Thread 가 분리되었기 때문에 Timer 가 돌아가는 Thread 에서 UI Thread 의 메서드나 컨트롤에 접근하기 위해서는 별도의 명령을 사용해야 하는 문제가 존재한다. 자~ 그럼 여기서 Timer 에 대해서 다시 한번 정리해 보도록 하자. .NET 에서 제공하는 Timer 들 .NET 에서는 기본적으로 3가지 Timer를 제공하고 있다. (MSDN) System.Windows.Forms.Timer - 사용자가 지정한 간격마다 이벤트를 발생시키며 Windows Forms 응용 프로그램에서 사용할 수 있도록 최적화 되어 있다. System...

[Logging] NLog 사용법 정리...

SCSF 에는 기본적으로 Enterprise Library가 사용된다. 예전에도 그랬지만 기능은 훌륭하고 많은 부분에서 최적화(?)된 것일지도 모르지만, 역시나 사용하기에는 뭔가 모르게 무겁고, 사용하지 않는 기능이 더 많다라는 느낌을 지울수가 없다. 이번 프로젝트도 SCSF를 기반으로 하고 있지만, Enterprise Library를 걷어내고 각 부분에 전문화된 오픈 소스를 사용하기로 하였다. 예전에는 Log4Net을 사용했지만, 대량 사용자 환경에서는 메모리 누수와 기타 문제점이 존재한다는 MS 컨설턴트(?)의 전해진 말을 들은 후로는 사용하지 않는다. 대안으로 사용하는 것이 NLog 이다. 조금 후에는 3.0 버전도 나온다고 홈 페이지에 기재되어 있지만, 그 때가 되면 프로젝트는 끝나기 때문에 현재 2.1.0 버전을 사용하기로 했다. [원본 출처] http://cloverink.net/most-useful-nlog-configurations-closed/ 위의 참조 자료에는 다양한 정보들이 존재하므로 꼭 링크를 통해서 관련된 정보를 확인하고 이해하는 것이 좋을 듯 하다. 여기서는 당장 필요한 부분만을 정리하도록 한다. [ Logger 찾기 ] 기본적으로 Logger가 존재하는 클래스를 기반으로 Logger 정보를 구성한다. Logger logger = LogManager.GetCurrentClassLogger(); 주로 Namespace 기반으로 Logger를 설정하는 경우에 유연하게 사용할 수 있다. 또 다른 방법으로는 지정한 문자열로 특정 Logger를 직접 선택하는 방법도 제공된다. 이를 혼용해서 Namespace와 직접 지정 방식을 같이 사용할 수도 있다. 물론 Logger 환경 설정에서 Wildcard (*)를 지정할 수도 있다. Logger logger = LogManager.GetLogger("Database.Connect"); Logger logger = LogManager.Get...