반응형
반응형

이번에 WD 10TB를 구매하면서, 기존에 예비용으로 쓰던 USB 2TB 외장하드의 데이터를 10TB로 옮기는 작업을 주말간 진행했다.

 

그동안 모았던 자료들은 대부분 RSS 통해서 받아진 자료이고, 관리를 안하다보니 2TB를 거의 꽉 채운 데이터를 옮겨야 했었고, GUI 환경은 아무래도 대용량 처리에 적합하지 않으므로 rsync로 옮기기로 결정!

 

데이터가 적을 경우에는 Rsync를 쓰던 cp를 쓰던 mv를 쓰던 사실 문제가 안된다.

하지만, 우리에겐 시간이 없기 때문에 최대한 고효율을 뽑아야 한다.

 

그러던 중 머리에 떠오른 것은 Rsync가 Single Thread이지만, xargs를 통해 Multiple Thread처럼 처리를 하면 좀 더 효율적이지 않을까하는 생각이 들었다. (결국은 Disk-iops의 한계가 있으므로 Thread를 적정량 설정해야 하겠다.)

 

아래와 같은 커맨드를 이용해봤다.

Source Directory의 목록을 ls로 가져오고, xargs를 통해 rsync와 연계하는데 쓰레드는 5개로 세팅

 

$ ls /root/2T/Video/TV | xargs -I {} -P 5 -n 1 rsync -avz --progress /root/2T/Video/TV/{} /root/10T/Video/TV/

 

이 명령어를 실행하고, 프로세스 목록을 보면, 서브프로세스들이 생성되며 작업을 잘 나눠서 처리하는 것을 볼 수 있다.

앞으로도 효율적인 대용량 데이터 처리에 참고해야겠다.

반응형
,
반응형

코로나19 때문에 집에서 한 2주를 쉬니깐(회사에서 나오지 말라고 했음), 게임만 하다가 지쳐서.. 공부를 해야겠다는 욕구가 들어 페이스북을 켰고(?), 좋은 자료를 발견하게 되어, 오랜만에 포스팅을 함.

 

기존에 MySQL을 운영해본 경험으로 보면, 기본 제공하는 기능만으로 절대 HA(고가용성)을 지원할 수 없는 것이 MySQL 인 것 같다. 

 

경쟁사 제품인 MS SQL Server와 비교했을 때도, 성능 면에서라던지 여러가지 기능이 부족해보이긴 하지만, 결론적으로 사용하다보면 DBMS 자체가 다 비슷하다고 본다. 유료냐 무료냐의 차이일 뿐.

 

MySQL은 HA를 지원하지 않지만, Replication은 지원을 하기 때문에, 데이터가 유실될 경우 Replication 구성을 통해 복원도 가능하며, 사용자가 은근히 많아서 지원하는 툴도 많다. 유료버전을 쓰면 Cluster구성도 가능하다고 하는데, 돈주고 굳이 그렇게 쓰는사람이 몇이나 될까?

 

MySQL을 운영하다보면, Replication만으로 운영을 하는 것은 한계가 있다는 것을 깨닫고, 좀 더 효율적인 분산을 위해 Application 레벨에서의 Sharding도 구현을 해보고, Redis를 앞에 둬서 Cache Aside 패턴으로 DBMS의 부하를 줄이는 방법도 고려를 했다.

 

다만, 이 방법들은 서비스해야 하는 Application Server가 동시접속자를 10만 ~ 100만정도가 예상될 경우, DBMS에서 감당해야하는 Connection 문제가 생기게 된다.

 

동시접속자 100만을 받아야한다고 칠 때, Application Server하나가 동시접속자 5천을 커버하고, DBMS 하나가 2만명을 받아야 한다고 쳐보면, Application Server가 200대, DBMS가 50대 필요할 것이고, Application 레벨에서 Connection Pool을 구현해서 최소 30개의 Connection을 유지해야 한다면, DBMS가 감당해야 하는 Connection 수는 상상을 초월하게 될 것이다. (30 * 200 * 50 = 30만!)

 

이런 이슈를 피하기 위해, 중간에 Middle Ware 개념의 Query Dedicate Server를 만들어서 운영하는 경우도 있긴한데, 구현 및 관리하기가 어렵고, 일반적으로 자체구현한 Middle Ware가 DBMS Native Protocol을 지원하는 것은 쉽지 않으므로, 성능이슈가 생기게 된다.

 

이런 것들을 해결해줄만한 아래와 같은 솔루션이 있었다는 것을 최근에 알게 되었다.

https://www.proxysql.com/

 

이 솔루션이 해주는 역할은 명확하다.

 

1. Application과 DBMS 사이에서 Middle Ware역할을 해주면서, Connection Multiplexing을 제공해준다.

(Connection Multiplexing이란, 출발지에서 요청한 수 많은 커넥션이 있을 때, 그 커넥션의 성격이 비슷한 경우 하나의 커넥션으로 인식하여, 효율적으로 사용할 수 있도록 해주는 기능을 의미한다.)

 

2. MySQL Native Protocol을 지원하기 때문에, Application에서 MySQL 붙는 것처럼 IP정보만 교체해주면 되고, 그렇기 때문에 성능 이슈가 없을 거라고 생각됨.

 

3. Query를 Routing할 수 있도록 Admin기능을 제공해준다. 

(ProxySQl이 제공하는 Meta Table에다가 MySQL Host들을 Seq 기반으로 등록하고, 출발지의 Connection의 정보 중 MySQL User와 MySQL DB정보를 읽어서 내가 원하는 MySQL Host로 보낼 수 있도록 제공)

 

3번 같은 경우는 HA Proxy와 L7을 이용해서도 충분히 만들어낼 수는 있는데, Connection Multiplexing이 안되는 것은 둘째치고, HA Proxy 설정하는 것이 만만치는 않아서, 실 서비스에 사용을 고려하진 못했었다.

 

좋은 솔루션을 알게 되었으니, 직접 세팅해보면서 이후에 추가로 포스팅해야겠음.

반응형
,
반응형

podman을 사용하면, container 표준기술이기 때문에, docker처럼 docker service daemon에 의존성 없이 container관리가 가능하다는 소식을 듣고, 설치부터 container 실행까지 해보던 중, 이미지를 가져올 수 없다는 에러가 발생하여, 그에 대한 해결책을 정리한다.

 

우선 podman 설치는 ubuntu 기본 repo에서 제공하지 않으므로, 별도의 repo를 등록해야 하고, 14.04 같은 예전버전에서는 repo를 등록해도 패키지를 찾을 수 없어서, 18.04에서 설치를 해서 진행을 했다.

(참고 : podman 설치 : https://computingforgeeks.com/how-to-install-podman-on-ubuntu/)

 

podman이 가장 좋은 게, docker / docker-compose.yml을 거의 그대로 활용이 가능하다.

다만, docker-compose 명령어를 그대로 쓰는 건 아니고, podman-compose라는 모듈을 별도로 설치해야 하는데, 아직 exec / ps 키워드는 개발이 덜 된 듯 하다.

(참고 : podman-compose 설치 : https://github.com/containers/podman-compose)

 

docker-compose.yml을 podman-compose를 이용해서 up을 했는데, 이미지를 땡겨올 수 없다는 에러가 발생했다.

(Error: unable to pull redis: image name provided is a short name and no search registries are defined in the registries config file.)

 

podman이 docker의 키워드를 모두 이용할 수는 있지만, 기본적으로 바라보는 repository가 설정이 되어 있지 않아 발생하는 에러이며, /etc/container 경로에 registries.conf 파일을 생성하여, 아래 내용을 입력해줌으로써, podman이 docker.io repo를 참조할 수 있게끔 설정해주면 된다. 


# This is a system-wide configuration file used to
# keep track of registries for various container backends.
# It adheres to TOML format and does not support recursive
# lists of registries.

# The default location for this configuration file is /etc/containers/registries.conf.

# The only valid categories are: 'registries.search', 'registries.insecure',
# and 'registries.block'.

[registries.search]
registries = ['docker.io']

# If you need to access insecure registries, add the registry's fully-qualified name.
# An insecure registry is one that does not have a valid SSL certificate or only does HTTP.
[registries.insecure]
registries = []


# If you need to block pull access from a registry, uncomment the section below
# and add the registries fully-qualified name.
#
# Docker only
[registries.block]
registries = []

 

 

반응형
,
반응형

쿼리 결과를 텍스트로 뽑아내는 것 까지는 성공했는데, ANSI 인코딩으로 파일이 만들어지는 바람에,

텍스트에 한글이 포함된 경우 해당 텍스트를 받아야하는 로그 서버에서 인코딩 에러가 발생을 하였다.

 

utf-8로 인코딩을 위해 sqlcmd의 옵션들을 검토하다가 해당 옵션을 발견함!


sqlcmd -E -o "output할 파일의 절대경로" -Q "실행할 쿼리" -f 65001 -h-1 -W

 

[옵션에 대한 설명]

-o : output할 파일명 (가급적 절대경로)

-Q : 실행할 쿼리 (SP도 가능함)

-f 65001 : 이것이 파일로 output하면서 ANSI를 utf-8로 변환해주는 옵션이다.

-h-1 : sqlcmd로 쿼리 결과를 파일로 뽑아낼 경우 상단에 ----------------- 가 붙는다. 이걸 제거해주는 역할

-W : sqlcmd로 쿼리 결과를 파일로 뽑는 경우 어마어마하게 공백이 추가되어 쓸데 없이 byte수가 늘어난다. 공백을 제거해주는 역할을 한다.

반응형
,
반응형

1. 전역 변수 Assign은 어떻게?


<body th:with="str='str',num=1,num2=2">

문자열 입력 시, 필히 quote로 묶어주어야 한다.

 

 

2. 페이지 Include는?


<div th:include="include1">

.html은 생략할 수 있으며, 모든 예제가 div로 되어 있어서 div를 넣을 지 말지 많이 고민했는데,

thymeleaf는 freemarker와는 다르게 어쩔 수 없이 하나의 태그가 포함되어야 하므로,

적당히 div를 통해 묶어주던가 css를 조작하여 페이지에 최대한 영향이 없는 태그를 넣는다.

그것도 싫다면 th:block을 사용하면 된다! (이게 더 좋을듯)


<th:block th:include="include1">

 

 

3. 조건문 사용 시 || 나 && 사용이 불가능함.


<div th:if="${abc} == 1 or ${abc} == 2">

영어로 작성해주어야 한다. 물론 &&는 and로 적어야 함.

 

 

4. Loop는 어떻게 할까?


<ul th:each="v : ${values}">
  <li th:id="${v.id}">
</ul>

include와 마찬가지로 freemarker처럼 html 포맷을 무시한 태그들 중앙에 loop을 돌릴 순 없고, 태그 하나가 필요하다.

사용법은 freemarker와 크게 다르진 않다.

 

 

5. 값이 없는 경우 다른 값으로 대체 하기


<input th:value="${abc != null} ? ${abc} : 123">

일반적인 프로그래밍 언어에서 지원하는 3항 연산자가 가능! 하지만 freemarker보다 불편한건 함정

조건문을 중괄호로 전부 묶어주는게 포인트다.

 

 

6. thymeleaf로 전환하니깐 IDE에서 계속 노란불(Warning)이 들어오는 경우.


<html xmlns:th="http://www.thymeleaf.org">

사실 무시해도 되는 Warning인데, 성격상 노란불 들어오는 것을 용납을 못하기 때문에 찾아봤다.

상단에 저렇게만 추가해줘서, html에게 th가 뭔지만 알려주면 된다.

 

 

7. onclidk 이벤트에 javascript:xxxxx('${abc}') 형태로 문자열 연결을 하려는데 타입이 다른 변수들은 연결할 수 없다고 에러가 나는데 어케함?


<button type="button" th:onclick="javascript:send([[${abc}]])">send click!!</button>

사실 전부 문자열인데, 왜 에러가 나는 지 이해가 잘 안됐고, 문자열과 변수를 묶어줄 때 사용하는 | 도 먹히질 않았음.

저렇게 대괄호로 변수 부분만 묶어주면, quote도 알아서 들어가고 심플하다.

 

 

드디어 구닥다리 freemarker에서 벗어나 thymeleaf로 오게 되었음.

바꾸고 나니깐 뭔가 렌더링도 더 빨라진 느낌이다.

 

반응형
,
반응형

내부에 기괴한 시스템에서 POST Method밖에 지원을 못한다고해서, nginx에서 html을 업로드하고 405 method not allowed 에러를 우회할 수 있는 방법에 대해 알아보았다.

 

405에러가 발생할 경우 무시하고 uri를 돌려줘라라는 의미인데, 의외로 잘 동작한다.

location / {                
    root   /var/www/html;
    index  index.html index.htm;
    error_page 405 = $uri;
}

 

파라미터 주고 받는거 전혀 없는 static page를 POST Method로 굳이 호출해야 하는 것도 좀 이상하긴하지만, 사내에 기괴한 시스템 등에서 POST Method밖에 허용을 못하겠다고 한다면, 이렇게 해결하면 된다.

반응형
,
반응형

python도 그렇고, windows도 그렇고 escape처리가 다른 언어 및 OS에 비해 까다롭기 때문에, win_command 처럼 문자열을 많이 입력해야 하는 모듈의 경우 escape 처리에 신경을 많이 써야 한다.

 

msi 파일 등을 무인 설치 하고, 설치된 디렉토리에 가서 뭔가 명령어를 입력해야 하는 경우, C:\Program Files 같이 중간에 띄어쓰기가 있는 경우 최근에 삽집을 했기 때문에 해당 내용에 대해 정리함.

 

C:\Program Files\Abc eef 경로에 있는 execute.cmd를 -r argument까지 넣어서 실행해야 하는 경우 아래와 같이 하면 문제가 없다. (참고로 win_shell 모듈 사용의 경우에는 powershell 기반이라 그런지 첫 글자에 \가 붙어서 문제가 발생하게 된다.)

---
 - name: test escape string
   win_command: '"C:\Program Files\Abc eef\execute.cmd" -r'

 

다음 예시로, 위와 동일한 경로에서 argument까지 넣어서 실행 한 후 추가 argument에 ansible_fact에서 가져온 정보까지 넣어야 하는 경우 아래와 같이 하면 된다.

---
 - name: test escape string with ansible_facts
   win_command: '"C:\Program Files\Abc eef\execute.cmd" -r {{ ansible_facts["hostname"] }}'

 

두 개의 예시를 보면 알겠지만, 띄어쓰기가 포함된 경로는 Double quote로 묶어주는 것이 핵심이고, 경로만 묶는 것이 아닌 실행파일까지 한 번에 묶는 것이 중요하다. 그리고 전체 커맨드를 Single quote로 묶어주는 것도 중요하며, ansible_facts를 기본적으로 Single quote로 많이 사용하는 데, 전체 커맨드를 Single quote로 묶어준 경우 Double quote로 바꿔서 사용해도 문제가 없다.

반응형
,
반응형

Ansible의 Windows 모듈 중에 win_package에서는 특정 URL에서 msi 파일이나 exe 파일을 다운받아서 설치하는 과정까지 한 번에 해주는 기능을 제공한다.

 

아래와 같이 작성하면 일반적으로 문제가 없을 거라고 생각했다.

...
 - name: win_package test
   win_package:
     path: MSI파일을 받을 URL 입력
     arguments:
       - /quiet
...

 

실행해보면 아래와 같은 Fatal 에러가 발생한다.

fatal: [x.x.x.x]: FAILED! => {"changed": false, "msg": "product_id is required when the path is not an MSI or the path is an MSI but not local", "reboot_required": false, "restart_required": false}

 

MSI파일이 로컬 내에 위치해 있지 않은 경우 product_id를 넣으라는 의미인데..

Windows를 잘 모르는 내 입장에서 product_id를 어떻게 찾아야 할 지 막막해서 구글링을 해본 결과 다음과 같은 링크를 찾게 됨.

powershell을 이용해서 msi 파일의 정보를 뽑아내는데, 그 중에 ProductCode (ansible에서 이야기하는 product_id)를 뽑아낼 수 있다는 내용이다.

https://www.scconfigmgr.com/2014/08/22/how-to-get-msi-file-information-with-powershell/ 

 

How to get MSI file information with PowerShell

If you’re an ConfigMgr administrator like me, you will often find yourself in the situation where you may want to get the Product Code from a MSI file. Or perhaps you’re interested in the Product Version or simply just the Product Name. A while back I wrot

www.scconfigmgr.com

 

혹시 위 링크의 정보가 사라질 수 있으므로, Powershell 스크립트와 실행법에 대해 이 글에 복붙을 해보기로 한다.

param(
    [parameter(Mandatory=$true)]
    [ValidateNotNullOrEmpty()]
    [System.IO.FileInfo]$Path,
 
    [parameter(Mandatory=$true)]
    [ValidateNotNullOrEmpty()]
    [ValidateSet("ProductCode", "ProductVersion", "ProductName", "Manufacturer", "ProductLanguage", "FullVersion")]
    [string]$Property
)
Process {
    try {
        # Read property from MSI database
        $WindowsInstaller = New-Object -ComObject WindowsInstaller.Installer
        $MSIDatabase = $WindowsInstaller.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $null, $WindowsInstaller, @($Path.FullName, 0))
        $Query = "SELECT Value FROM Property WHERE Property = '$($Property)'"
        $View = $MSIDatabase.GetType().InvokeMember("OpenView", "InvokeMethod", $null, $MSIDatabase, ($Query))
        $View.GetType().InvokeMember("Execute", "InvokeMethod", $null, $View, $null)
        $Record = $View.GetType().InvokeMember("Fetch", "InvokeMethod", $null, $View, $null)
        $Value = $Record.GetType().InvokeMember("StringData", "GetProperty", $null, $Record, 1)
 
        # Commit database and close view
        $MSIDatabase.GetType().InvokeMember("Commit", "InvokeMethod", $null, $MSIDatabase, $null)
        $View.GetType().InvokeMember("Close", "InvokeMethod", $null, $View, $null)           
        $MSIDatabase = $null
        $View = $null
 
        # Return the value
        return $Value
    } 
    catch {
        Write-Warning -Message $_.Exception.Message ; break
    }
}
End {
    # Run garbage collection and release ComObject
    [System.Runtime.Interopservices.Marshal]::ReleaseComObject($WindowsInstaller) | Out-Null
    [System.GC]::Collect()
}

 

실행법은 파워쉘을 연 후에 다음과 같은 커맨드를 입력하면 된다.


.\파일명.ps1 -Path "MSI파일의 경로" -Property ProductCode

위의 powershell 스크립트를 이용해서 win_package 모듈 사용 시 아래와 같이 product_id를 추가해주면 에러가 사라지게 된다.

...
 - name: win_package test
   win_package:
     path: MSI파일을 받을 URL 입력
     product_id: Powershell 스크립트를 이용해 얻은 product_id (single quote를 붙여주자.)
     arguments:
       - /quiet
...

 

반응형
,
반응형

우선 위 에러가 발생하는 경우는 playbook이든 ansible이든 become 메소드를 이용하여 sudo 권한을 획득하려 했으나, Windows 머신 등이 inventory에 포함되어 있는 경우 sudo 가 없기 때문에 발생한다.

 

Windows 머신의 경우 become 메소드 없이도, Administrator 계정을 inventory에 넣으면 비슷한 효과가 있기 때문에 inventory에서 become 메소드를 아예 활용하지 않도록 아래와 같이 예외처리가 가능하다.

[windows:vars]
ansible_become=false

[windows]
localhost1
localhost2
localhost3

 

ansible_become을 inventory에 선언해두면, playbook에서 become이 yes로 되어 있더라도, 해당 host들은 inventory에 선언해둔 값을 우선 시 하기 때문에, 문제가 발생하지 않는다.

반응형
,
반응형

playbook 작성 시 상단에 gather_facts 여부를 지정할 수가 있다.

playbook 하나로 모든 OS에 대해서 대응을 해야 하는 경우, OS에 대한 종류가 많아 분기문이 많이 들어가야 하는 경우에 ansible_facts를 사용해서 해당 하는 서버의 내부 정보를 수집하고, 그 수집된 정보 중 OS의 정보를 가져와서 해당 값들을 분기문에 추가할 수가 있다.

이런 경우 gather_facts를 yes로 지정해 주어야 한다.

 

분기문에 추가하는 경우는 아래와 같이 심플하게 추가할 수 있다.

---
 - hosts: all
   gather_facts: yes
   tasks:
     - name: check ubuntu14
       debug:
         msg: "ubuntu 14!!!"
       when:
         - ansible_facts['distribution_file_variety'] == "Debian"
         - ansible_facts['distribution_major_version'] == "14"

 

inventory에 linux 장비들만 포함되어 있다면 크게 문제가 없이 실행이 된다.

하지만, windows 머신이 inventory에 하나라도 포함되어 있다면, gather_facts시 linux 에서만 수집할 수 있는 ansible_facts['distribution_file_variety'] 값이 없기 때문에 에러가 발생할 수 있다.

 

이런 경우에는 when 문에 아래와 같이 is defined 문을 추가해줌으로써 해결할 수 있다.

---
 - hosts: all
   gather_facts: yes
   tasks:
     - name: check ubuntu14
       debug:
         msg: "ubuntu 14!!!"
       when:
         - ansible_facts['distribution_file_variety'] is defined
         - ansible_facts['distribution_file_variety'] == "Debian"
         - ansible_facts['distribution_major_version'] == "14"

 

일반적인 프로그래밍 언어의 if문과 같은 매커니즘 이므로, is defined 문은 가장 상단에 위치해야 원하는대로 동작하게 된다.

 

이제 facts를 수집해서 분기문을 처리하는 부분까지는 완료를 했으나, 수집된 facts를 msg 등의 string에 포함시켜서 출력해야 하는 경우가 있을 수 있는데, 이런 경우 ansible에서는 jinja2 엔진을 내장하고 있기 때문에 jinja2 문법을 활용하면 된다.

 

아래는 debug모듈의 msg에 ansible에서 수집한 facts 값을 출력하는 예시를 들어보았다.

---
 - hosts: all
   gather_facts: yes
   tasks:
     - name: check ubuntu14
       debug:
         msg: "ubuntu 14!!! file_variety is {{ ansible_facts['distribution_file_variety'] }} !!"
       when:
         - ansible_facts['distribution_file_variety'] is defined
         - ansible_facts['distribution_file_variety'] == "Debian"
         - ansible_facts['distribution_major_version'] == "14"

 

 

반응형
,
반응형