블로그 이미지
평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-bwcho75골뱅이지메일 닷컴. 조대협


Archive»


 
 

마이크로 서비스 아키텍쳐와 컨테이너

조대협 (http://bcho.tistory.com)

모노리틱 아키텍처

마이크로 서비스 아키텍쳐를 이해하려면 먼저 이에 상반되는 모노리틱 아키텍쳐를 이해할 필요가 있다. 모노리틱 아키텍쳐는 전통적인 아키텍쳐 스타일로 애플리케이션이 하나의 서버에 배포 되고, 데이타 베이스도 마찬가지로 하나의 데이타 베이스에 모든 데이타를 저장하는 방식이다.


예전에 하나의 큰 서버를 놓고, 그 안에 하나의 애플리케이션으로 개발하는 방식인데, 수퍼돔과 같이 큰 머신을 하나 놓고, 오라클 데이타베이스에 모든 데이타를 저장하고, 애플리케이션 바이너리를 하나로 개발하는 방식이다. 중앙 관리된 구조에서 통제가 편리하고, 같은 솔루션을 사용한다는데 있어서 장점이 있다.

마이크로 서비스 아키텍처

마이크로 서비스 아키텍처 (Micro service architecture,이하 MSA)는 비지니스 기능 마다 애플리케이션 서버와 데이타 베이스 서버를 분리하는 방식이다.

아래 그림과 같이 상품 정보, 상품 리뷰, 상품 상세 정보 서비스를 각각 별도의 애플리케이션으로 나누고 데이타 베이스도 각각 나누는 방식이다.  


이렇게 서버를 나누는 이유는 몇가지가 있는다.

첫번째는 서비스를 개발하는 조직이 다른 경우, 각 조직에 서비스 기획,개발,운영에 대한 독립적인 권한을 부여함으로써, 각 서비스 개발을 할때 다른 서비스에 대한 의존성이 없이 빠르게 개발할 수 있게 하는 목적을 가진다.

두번째는 인프라의 변화에 의한 요인을 들 수 있는데, 기존에는 수억의 고성능 서버에 하나의 애플리케이션을 넣는 방식을 이용했다면 근래에는 x86 서버기반으로 상대적으로 저비용,저성능 서버를 더 많이 사용하는 방식을 이용하기 때문에 하나의 큰 애플리케이션을 만드는 것보다, 작은 애플리케이션으로 나눠서 여러 서버에 분산하는 것이 유리하기 때문이다.

모노리틱 아키텍쳐와 비교

모노리틱 아키텍쳐에 비해서, 마이크로 서비스 아키텍쳐는 앞에서 언급한바와 같이 서비스 개발에 대해서 독립성을 가짐으로써 개발의 속도를 높일 수 있는 장점이 있고, 이외에도 서비스별로 다른 기술을 사용할 수 있다는 장점이 있는데, 이는 서비스 특성에 따라서 적절한 기술을 사용할 수 있다는 것을 의미한다.


반드시 장점만 있는 것이 아니라, 단점도 있는데, 여러가지 기술을 혼용해서 사용하다 보니, 그 기술의 표준을 통제하기가 어렵다. 희귀한 기술로 개발을 진행한 서비스가 있을때 개발자가 팀을 떠나게 되면, 해당 서비스를 계속해서 유지하기 어려울 수 있고, 같은 기술을 사용하더라도 프레임웍이나 코드 표준화 개발 프로세스 표준화 등에서 어려움을 겪을 수 있다.

마이크로 서비스 아키텍쳐가 어려운점 중의 하나는 서비스가 증가될 수 록, 서비스간의 연계가 복잡해져서 장애가 발생했을때 어느 서비스에서 장애가 발생했는지 찾아서 조치하는 것이 어렵다는 것이다. 그래서 마이크로서비스에서는 특히 분산된 서비스간의 모니터링을 구현하는데 더 많은 노력을 기울여야 한다.

조직 구조

마이크로 서비스의 목적은 서비스를 작은 단위로 쪼겐 후에, 각 팀이 각 서비스에 대한 개발과 운영 전체에 대한 책임을 부여함으로써, 서비스 개발의 속도를 높이는데 있다. 그래서, 마이크로 서비스 아키텍쳐를 적용하는데 있어서 가장 중요한 것은 조직 구조이다.


소프트웨어 아키텍쳐와 조직문화에 대해서 재미있는 이론이 하나 있는데, 콘웨이의 법칙이다.  콘웨이의 법칙은 “소프트웨어 아키텍쳐는, 소프트웨어를 개발하는 팀의 구조를 따라간다" 라는 이론이다.

이를 재 해석하면 마이크로 서비스 아키텍쳐에서 서비스의 분류 단위는 이를 개발하는 팀의 단위가 된다.


아무리 아키텍쳐를 논리적으로 잘 설계해서 서비스별로 나눴다하더라도, 팀의 구조와 맞지 않으면, 결국에는 아키텍쳐가 무너진다. 예전에 시스템을 설계할때, 미국,캐나다,인도,한국팀으로 팀을 나눠서 일한적이 있었다. 마이크로 서비스 아키텍쳐를 도입했었는데, 개발을 진행함에 따라서 서비스 컴포넌트의 기능이 처음 설계대로 진행되지 않고 한쪽으로 몰리는 현상이 발생하게 되었다. 한국에 기획이 있었기 때문에 비지니스에 연계된 결정은 대부분 한국에서 나오게 되었고 많은 커뮤니케이션이 필요하였기 때문에, 한국팀이 상대적으로 비지니스에 연계된 중요 컴포넌트를 많이 맏게 되었다. 미국과 캐나다는 기술이 상대적으로 좋았기 때문에 기술 난이도가 높은 기능들이 미국과 캐나다 서비스로 몰리게 되었고, 시간대가 같기 때문에 커뮤니케이션이 활발하게 일어났고 결과적으로 미국과 캐나다 서비스는 타이트하게 서로 맞물리는 구조가 되었다.

인도의 경우 시간대가 다르고 커뮤니케이션의 어려움 때문에 한번 커뮤니케이션하는데 시간이 많이 소요되었고 그로 인해서 상대적으로 덜 중요한 기능이 인도팀으로 점점 배정되었다.


사례에서 볼 수 있듯이, 마이크로 서비스 아키텍쳐 설계에서 가장 중요한 점은 기술적으로 아키텍쳐를 잘 설계하는 것이 아니라, 팀의 구조를 아키텍쳐에 맞는 구조로 만들고, 그 안에 원할한 커뮤니케이션 환경을 만들어주는 것이 가장 중요하다.  

조직문화

팀을 분할해서 독립성을 부여해서 개발의 속도를 높일려면 단순하게 팀만을 분할해서는 불가능하다. 각 팀이 맏은 컴포넌트 또는 서비스에 개발에 대한 독립적인 책임을 부여하려면 권한도 같이 부여해야 한다.

기존의 모노리틱 시스템의 개발팀을 운영하는 구조가 중앙 조직이 의사 결정을 하고 전체 프로젝트를 진행하는 중앙 집중형 거버넌스 모델이었다면, 마이크로 서비스 아키텍쳐는 설계와 프로젝트 진행 각각에 대한 의사 결정을 각 서비스 팀에 내리는 분산형 거버넌스 구조가 바람직하다. 중앙팀에서 의사 결정을 받고 그에 따라 개발을 진행하면 커뮤니케이션 오버헤드가 발생하고 그로 인하여 빠른 개발이 어렵기 때문에 그 권한을 개발 주체에게 분산하는 모델이다.


의사 결정 권한을 각 팀에게 분산하려면 각 팀이 의사 결정을 할 수 있는 능력이 있는 팀 구조가 되어야 한다.

팀내에서 의사 결정을 하고 스스로 서비스를 개발할 수 있는 능력을 갖춰야 하는데, 이를 위해서는 기획,개발, 테스트 및 운영등 모든 역할이 한 팀안에 있어야 한다. 팀의 크기는 보통 5~8명 정도가 적절한데, 이를 2-피자팀이라고도 이야기 한다. (2개의 피자로 팀원들이 모두 먹을 수 있는 정도의 팀의 크기) 이 인원을 넘어서면 커뮤니케이션이 어려워지고 작으면 제대로 된 서비스를 개발하기 어렵기 때문에 이 정도 팀의 크기를 추천한다.


DEVOPS

Devops는 운영과 개발을 한팀에서 하는 모델을 말하는데, 팀이 개발과 운영을 모두 담당함으로써 개발과 운영 사이에서 오는 간극을 해결하고 개발된 시스템을 빠르게 배포하고, 운영 과정에서 얻은 노하우를 개발에 반영해서 시장의 요구 사항에 빠르게 반응 하는데 그 목적을 둔다.

개발과 운영을 한팀에서 담당함에도 불구하고,  Devops 엔지니어, SRE (Site Reliability engineer)등과 같이 기존의 운영팀이 하던 일을 하는 역할이 여전히 남아 있는데, 그렇다면 Devops로 넘어왔음에도 불구하고 이러한 역할이 계속 남아 있는 이유와 정확한 역할은 무엇일까?


앞에서도 언급했듯이 Devops는 개발팀이 개발/배포/운영을 모두 담당하는 셀프 서비스 모델이다. 셀프 서비스를 하기 위해서는 인프라가 플랫폼화 되어 있어야 한다. 개발팀이 직접 데이타 센터에 가서 서버를 설치하고 OS를 설치하고 네트워크 구성을 하기는 어렵고, 온라인으로 서버를 설치하고 네트워크를 구성할 수 있어야 하고, 무엇보다 쉬워야 한다. 인프라 구성뿐 아니라 그위에 소프트웨어를 쉽게 빌드 및  배포하고 운영 중인 시스템에 대한 모니터링이 가능해야 하는데, 이러한 인프라를 일일이 구성하기는 어렵기 때문에 플랫폼화가 되어 있어야 하는데, Devops 엔지니어의 역할은 이러한 플랫폼을 만드는 역할이 된다.



위의 그림과 같이 Devops 팀은, 시스템을 실행할 수 있는 런타임 인프라를 개발 배포하고, 런타임 시스템에 대한 모니터링과 로깅을 제공하며, 이 시스템에 자동으로 배포할 수 있는 CI/CD 플랫폼을 구축한다.

이렇게 개발된 플랫폼에 개발팀은 개발된 시스템을 스스로 배포하고 운영하는 모델이다.

이러한 모델은 구글의 SRE (Site Reliability Engineering)에서 좋은 사례를 찾아볼 수 있는데, SRE 엔지니어는 시스템이 개발된 후에, 인프라 시스템에 대한 플랫폼화 작업을 수행하고, 이 플랫폼이 완성되어 안정화 될때까지 지속적인 지원을 하며, 플랫폼에 대한 안정화 작업이 끝나면 플랫폼의 운영을 개발팀에 맏기고 다른 프로젝트를 위한 플랫폼 작업을 하는 방식이다.

컨테이너

이러한 플랫폼을 지원하기 위해서는 벤더 종속적이지 않고, 개발자가 손쉽게 운영 및 접근할 수 있는 인프라 관리 기술이 필요한데, 이런 기술로 많이 언급되는 기술이 컨테이너이다.




가상머신 (VM)의 경우에는 하이퍼바이저의 종류에 따라서, 호환이 되지 않는 경우가 있고, 무엇보다 가상 머신 이미지의 사이즈가 매우 크기 때문에 (수백~기가 이상) 손쉽게 이식하기가 쉽지 않다.

또한 하드웨어 계층 부터 가상화 하기 때문에 실행하는데 컨테이너에 비해서 상대적으로 많은 자원이 소요된다.

컨테이너의 경우 가상 머신을 사용하지 않고 호스트 OS의 커널에서 바로 실행이 된다. 실행되는 컨테이너의 OS가 호스트 OS와 다른 경우, 이 다른 부분 (알파)만을 컨테이너에서 추가로 패키징하여 실행이 된다.

예를 들어 호스트 이미지에 기능이 A,B,C가 있고, 컨테이너는 A,B,C,D가 필요하다면, 컨테이너에는 다른 부분인 D만 묶어서 패키징 하는 개념이다.  그래서 가상머신에 비해서 크기가 훨씬 작고 가상화 계층을 거치지 않기 때문에 훨씬 효율적이라고 말할 수 있다.

컨테이너 관리 솔루션

컨테이너를 소규모로 사용한다면 물리 서버를 직접 지정해서 배포하면 되지만, 대규모로 컨테이너를 운영하고자 할때는 어떤 서버에 컨테이너를 배치해야 하는 가에 대한 문제가 생긴다.


예를 들어 16 CPU, 32 GB 메모리 머신들에 컨테이너를 배포할때 컨테이너 사이즈가 2 CPU, 3 CPU, 8 CPU등 다양할 수 있기 때문에, 자원을 최대한 최적으로 사용하기 위해서 적절한 위치에 배포해야 하고, 애플리케이션 특성들에 따라서, 같은 물리 서버에 배포가 되어야 하거나 또는 가용성을 위해서 일부러 다른 물리서버에 배포되어야 하는 일이 있다. 이렇게 컨테이너를 적절한 서버에 배포해주는 역할을 스케쥴링이라고 한다.



이렇게 컨테이너 스케쥴링을 해주는 솔루션으로는 Kubernetes, Mesosphere, OpenStack 등 다양한 솔루션이 난립해서 혼돈이었는데, 작년말 (2017년말)을 기점으로 해서 쿠버네티스가 de-facto 표준으로 되어가는 형국이다. 아래 트랜드 그래프에서 보면 알 수 있듯이 쿠버네티스의 트랜드가 지속적으로 올라가서 가장 높은 것을 확인할 수 있다.



또한 주요 클라우드 벤더인 아마존,구글,애저 모두 컨테이너 관리 환경을 쿠버네티스를 지원하는 정책으로 변화된것은 물론이고 IBM이나 시스코와 같은 온프렘(on-premise) 솔루션 업체들도 경쟁적으로 쿠버네티스를 지원하고 있다.

가상 머신위의 컨테이너

보통 이런 컨테이너 환경을 운영할때 베어메탈 (하드웨어)위에 바로 컨테이너 솔루션을 올리지 않고, 가상화 환경을 올린 후에, 그 위에 컨테이너 환경을 올리는 경우가 많다.


베어메탈 위에 바로 컨테이너 환경을 올리면 성능적 이점도 있고, 계층도 줄어들어 관리도 편리한데, 왜 가상화 계층을 한번 더 두는 것일까? 이유는 컨테이너 환경을 조금더 유연하게 사용할 수 있기 때문이다. 먼저 가상 머신을 이용해서 컨테이너 환경을 isolation할 수 있고, 가상화를 통해서 자원의 수를 더 늘려서 이를 잘게 쪼게서 사용이 가능하다. 예를 들어 설명하면, 8 CPU 머신을 쿠버네티스로 관리 운영하면, 8 CPU로밖에 사용할 수 없지만, 가상화 환경을 중간에 끼면, 8 CPU를 가상화 해서 2배일 경우 16 CPU로, 8배일 경우 64 CPU로 가상화 하여 좀 더 자원을 잘게 나눠서 사용이 가능하다.



아두이노 nodemcu로 온습도계를 만들어 보자


조대협 (http://bcho.tistory.com)


nodeMCU 개발환경 설정이 끝났으면 간단한 애플리케이션을 하나 만들어보자

온습도계를 만들어보도록 한다. 온습도를 측정하여 LED로 출력하는 모듈을 개발해보겠다.

개발이 끝나고 나서 아두이노 개발환경에 대한 결론 부터 이야기 하자면, 쉽다. 대부분의 파츠들에 대한 SDK가 제공되기 때문에 손쉽게 개발이 가능하다. 단 해당 파츠에 맞는 SDK를 찾는데 들억는 시간이 더 많다.

온습도계 센서 (DTH11)

개발에 사용할 온습도계 센서는 DTH11이라는 센서이다. 아래와 같이 생겼는데, 좌측이 데이타, 가운데가 3.3V, 가장 우측이 GND이다.



온도와 습도 두개를 측정하는데 데이타 단자가 하나이다. 아날로그 신호를 핀에서 직접 읽는 것이 아니라 SDK를 사용한다.  DTH11 라이브러리는 https://github.com/adafruit/DHT-sensor-library 에서 다운 받아서 사용하면 된다. Adafruit_sensor 라이브러리에 대한 의존성이 있기 때문에, 해당 라이브러리 https://github.com/adafruit/Adafruit_Sensor 도 같이 설치하도록 한다.


라이브러리 설치가 끝났으면, DTH11 센서를 브레드 보드에 설치한다 좌측을 GPIO G6 포트에, 가운데를 3.3V, 가장 우측은 GND에 연결한다.


I2C LCD

다음 습도와 온도를 출력하기 위해서 LCD를 사용한다. 여기서 사용한 LCD는 I2C LCD로 가로 16자로 2줄 (16x2) 를 출력할 수 있는 LCD이다.


앞판은 LCD가 있고




뒤에는 아래 그림과 같이 LCD 아답터가 붙어 있다.

  • VCC는 nodemcu의 Vin 핀에 연결한다. 이 핀은 5V의 전앞을 낸다.

  • GND는 nodemcu의 GND에

  • SCA는 nodemcu D2에

  • SCL은 nodemcu의 D1핀에 연결한다.



다음 이 LCD를 사용하기 위해서는 SDK를 설치해야 하는데, https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library 라이브러리를 다운 받아서 설치한다.


코드

이제 코드를 보자

#include <LiquidCrystal_I2C.h>

#include "DHT.h"


#define LED D5            // Led in NodeMCU at pin GPIO16 (D0).

#define DHTPIN D6

#define DHTTYPE DHT11   // DHT 11


LiquidCrystal_I2C lcd(0x27, 16, 2);

DHT dht(DHTPIN, DHTTYPE);

float h,t;

void setup() {

 pinMode(LED, OUTPUT);    // LED pin as output.

 dht.begin();


 // LCD setting

 lcd.begin();

 lcd.backlight();

 lcd.print("Hello, world!");

 

 // Serial communication setting

 Serial.begin(9600);   

 Serial.print("Hello nodemcu");

}

void loop() {

 digitalWrite(LED, HIGH);// turn the LED off.(Note that LOW is the voltage level but actually

                         //the LED is on; this is because it is acive low on the ESP8266.

 delay(200);            // wait for 1 second.

 digitalWrite(LED, LOW); // turn the LED on.

 delay(200); // wait for 1 second.

 h = dht.readHumidity();

 t = dht.readTemperature();

 lcd.setCursor(0,0);

 lcd.print("Humidity:");

 lcd.print(h);

 lcd.setCursor(0,1);

 lcd.print("Temp :");

 lcd.print(t);

 Serial.print("H:");

 Serial.print(h);

 Serial.print(" T:");

 Serial.println(t);

}


DHT11 관련 코드

DHT11을 사용하려면, 입력 포트를 정의해야 하고 센서의 종류를 정의해야 한다.

#define DHTPIN D6

#define DHTTYPE DHT11   // DHT 11


DHT dht(DHTPIN, DHTTYPE);


에서 핀은 D6로 지정하고 DHTTYPE은 DHT11로 정의하였다.

다음 센서를 가동 시키기 위해서 setup() 에서


 dht.begin();


와 같이 DHT 센서를 가동 시켰다. 다음은 센서에서 온도와 습도를 읽어와야 하는데, loop() 함수내에서


 h = dht.readHumidity();

 t = dht.readTemperature();


readHumidity()와 readTemperature() 함수를 이용하여, 습도와 온도를 읽어왔다.


LCD 관련 코드

LCD를 사용하려면 초기화를 해줘야 하는데


LiquidCrystal_I2C lcd(0x27, 16, 2);


로 초기화를 해준다. 0x27는 LCD의 주소로, D1,D2 핀을 사용할때 사용하는 주소이다.핀의 위치가 바뀌면 이 주소도 변경 되어야 한다. 핀에 위치에 따라 주소가 다르거나 또는 인식이 안되는 경우가 있는데, http://playground.arduino.cc/Main/I2cScanner 를 이용하면 LCD가 연결이 되어 있는지 아닌지, 그리고 LCD의 주소를 알려준다.

다음 인자인 16,2는 가로 16자에 세로 2자 LCD 임을 정의해준다.


다음 초기화를 해줘야 하는데, setup()함수에서

 lcd.begin();

 lcd.backlight();

 lcd.print("Hello, world!");


로 초기화를 해준다. begin()은 LCD를 시작하는 것이고 backlight()는 LCD의 백라이트를 키도록 하는것이다. 글자를 출력하고 싶으면 print(“문자열")을 이용하면된다.

초기화가 끝났으면, DTH11 센서에서 읽은 값을 출력해주면된다.

윗줄에 습도 아랫줄에 온도를 출력할것인데

 lcd.setCursor(0,0);

 lcd.print("Humidity:");

 lcd.print(h);

 lcd.setCursor(0,1);

 lcd.print("Temp :");

 lcd.print(t);


윗줄 첫번째 위치 부터 습도를 출력할것이기 때문에, 출력 위치를  setCursor(0,0)으로 해서 맨 앞칸 첫줄로 지정을 해서 습도를 출력하고, 다음 온도는 setCursor(0,1)로 해서 맨 앞칸 두번째 줄 부터 출력하도록 하였다.



<그림. 완성된 모습 >

다음글에서는 이렇게 수집한 정보를 HTTP 를 이용해서 서버로 전송하는 코드를 추가해보도록 한다.


참고 자료


맥 OSX에서 nodeMCU와 Wemos D1 환경 설정하기


조대협 (http://bcho.tistory.com)


아두이노 우노로 아두이노 개발을 시작하고 서버 통신을 위해서 ESP8266 계열인 ESP01 칩을 사용했는데,  ESP01은 연결도 까다롭고 소프트웨어 시리얼을 사용해서 SDK를 찾기 어려운점도 있었다. 개발하고자 하는 내용이 대부분 서버와 통신을 하는 부분이기 때문에, 보드를 우노에서 ESP8266 을 메인 MCU로 하는 보드로 변경하였다.


후보군으로 올른것이 nodeMCU v2와 Wemos D1 보드이다.


<그림 nodeMCU v2와 Wemos D1 호환 보드>


nodeMCU의 경우에는 크기가 작고 성능이 뛰어날뿐 아니라, 널리 사용되는 보드이기 때문에, SDK나 예제를 구하기 쉬울것이라고 생각하였고, Wemos D1은 ESP8266을 포함하고 있으면서도 아두이노 우노와 유사한 레이아웃과 GPIO 핀 배열을 가지고 있기 때문에, 일반적인 개발에 좀더 편리하지 않을까 싶었다.


맥을 사용하기 때문에, OSX에 맞춰서 개발환경을 설정해야 했다.

USB 드라이버 설치

nodeMCU를 맥에 연결해도 MAC에서는 USB 포트를 인식하지 못한다. 이유는 nodeMCU와 통신할 USB 드라이버가 없기 때문에, nodeMCU는 아래 그림과 같이 USB 통신을 위해서 CP2102라는 칩셋을 사용한다. 그래서 이 칩셋을 위한 드라이버를 설치해줘야 한다.




드라이버를 https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers 에서 받아서 설치하면 된다. 이 드라이버는 Kernel Extension 이라는 형태인데 커널을 확장하는 기능이기 때문에 보안적인 제약사항을 받는다. 설치를 하더라도 바로 반영이 안되는데,  이유는 커널 확장 기능을 설치하려면 보안 승인을 해야 한다. USB 드라이버를 설치하고 나면 System > Preference에서 Security & Privacy 부분을 보면 아래와 같이 Kernel extension이 loading 되는 것이 블록 되어 있는 것을 볼 수 있다. 오른쪽의 Allow 버튼을 누르면 승인이 되고, 정상적으로 Kernel extension이 설치 된다.


제대로 설정이 되었는지를 확인하려면 OSX에서 해당 포트를 인지했는지 보면 되는데,

%ls /dev/tty.*

를 실행하면 다음과 같이


tty.SLAB_USBtoUART 이름으로 포트가 인식된것을 확인할 수 있다.


보드 추가

다음으로는 아두이노 개발환경인 Sketch에서 nodeMCU 보드 타입을 등록해야 한다.

Sketch 툴에서 Arduino > Preference 를 선택한다.

다음 아래와 같이 화면이 나오면 “Additional Boards Managers URLs”에

http://arduino.esp8266.com/stable/package_esp8266com_index.json

주소를 넣는다.


이렇게 해주면 Sketch에서 사용할 수 있는 보드의 종류가 추가로 등록된다. 다음 nodemcu를 사용하도록 보드를 선택해야 하는데, Tools > Boards 메뉴로 가서 아래 그림과 같이 node MCU v1.0을 선택한다.




통신 포트를 선택하고 다음 통신 속도를 921600으로 선택한다. 다음 제품 스펙에 맞게 아래 그림에서 “CPU Frequency”를 160Mhz로 조정하여서 실행하였다.


이제 개발 준비가 끝나고 개발을 진행하면 된다.



Wemos D1 환경 설정하기

Wemos D1 환경 설정도 크게 다르지 않다. 다만 USB 칩을 CH341칩셋을 사용하기 때문에, 맞는 드라이버를 설치해야 한다. 설치 방법은 동일하고, 드라이버는 https://wiki.wemos.cc/downloads 에서 다운로드 받을 수 있다. 보드 매니져에 보드를 추가해야 하는데, esp8266 계열이기 때문에, 앞에 추가한 보드 메니져에 이미 wemos d1이 들어가 있기 때문에, 이를 선택해서 사용하면 된다.


참고


라즈베리 파이 호환 보드 조사


라즈베리 파이를 구입해놓고 보니 단순한 소형 리눅스 머신이나. IO를 위한 GPIO 포트가 있는것 빼고는 크게 다르지 않다. 아두이노 시리즈도 호환 보드가 있는것 처럼, 라즈베리파이도 호환 보드가 있을것으로 생각하고 찾아보니 당연히 있다.

Orange PI



우분투,Debian, 라즈베리 파이 이미지 실행 가능


이름

CPU/GPU

메모리

디스크

가격

오렌지 파이 PC+

1.6Ghz 4 core

600Mhz GPU (OPEN GL ES)

1G

8G Flash

37500

오렌지파이 PC2

64bit 4 core, GPU 내장

1G

2MB Flush

40000

오렌지파이 라이트

4 core, 600MHz GPU

512M


25000


Banana PI


국내에서는 판매하는 곳이 많지 않고, 알리바바나 아마존을 통해서 구입해야 하는데, 기존 호환 보드에 비해서 8Core CPU를 탑재한 모델(12만원)이 있는등, 성능이 뛰어나다. 가격은 고가인편. 성능이 좋은편인데, 국내에서 많이 보편화 되지 않은 것은 아무래도 가격 때문인듯

Asus Tinker Board


1.8 GHZ, 4 코어 CPU, 600 Mhz GPU, 2GB 메모리, 가격대는 15만원선 국내에서는 판매하는 곳이 그리 많지 않음


비글본 블랙



1GHz Arm CPU, GPU 지원, 512M, 4G Flash, WIFI 지원으로 약 13만원선


국내에서 구매가 편리하고 가격대가 좋은 보드로는 오렌지 보드가 가성비 측면에서 가장 좋지 않은가 싶다.

라즈베리파이 3B+가 64비트 쿼드 코어 + 1G 메모리에 43000원 정도의 가격을 형성하는데.

오렌지 보드 PC+의 경우 32비트 쿼드코어 + 1G 메모리에 8G 온보드 플래시를 지원하면서 37000원정도이다.  PC2 모델의 경우 64비트 쿼드코어에 1G메모리에 플래시는 탑재하지 않고 40000원 수준이다.

크게 가격적인 차이는 없지만, 세세한 스펙상의 차이가 있기 때문에, 호환 보드를 고민해도 좋지 않을까 싶은데, 가격적인 차이가 크지 않기 때문에, 개발을 시작하는 입장에 있어서는 코드나 장비 호환성 측면에서 라즈베리 파이로 시작하는게 좋을것 같고, 소형 장비를 개발할때는 오렌지 보드를 고민하는 것도 좋지 않을까 한다.


아두이노, ESP8266,ESP 32 성능 비교


와이 파이 통신 모듈을 사용해보니, ESP8266이나 ESP32를 MCU로 하는 보드들을 메인 보드로 사용하는 경우가 많아서, 아두이노 우노나 메가와 같은 보드 없이도 이 보드들만으로도 성능이 충분할까해서 성능 비교표를 찾아보았다. 일단 결론 부터 이야기 하면, 아두이노 시리즈는 성능이 비교가 안된다. ESP32가 가장 빠르고, ESP8266 모델만 해도 상대적으로 매우 높은 성능을 낸다.



소스 : https://hilo90mhz.com/arduino-esp32-esp8266-101-speed-test-comparison-chart/


ESP01 (ESP8266)을 이용한 HTTP 통신


조대협 (http://bcho.tistory.com)


하드웨어 시리얼과 소프트웨어 시리얼

앞의 글에서 ESP01을 연결해봤는데, ESP01 연결시에 포트를 2,3번 포트를 사용하고, 코드는 SoftSerial 라이브러리를 이용하였다. 아두이노 우노의 0,1 번포트는 시리얼 통신을 위한 RX,TX 포트이다. 이를 Hardware Serial 이라고 한다. 그러면 하드웨어 시리얼 포트를 사용하지 않고 2,3번 포트를 사용한 후에 소프트웨어 시리얼 처리를 한 이유는 무엇을까?


하드웨어 시리얼 포트는 PC와 연결되어 있을때 PC와 통신을 목적으로 사용된다. 그래서 하드웨어 시리얼을 사용하지 않은것인데, ESP8266 관련 라이브러리들을 살펴보니, 대부분 하드웨어 시리얼을 사용하는 것으로 보인다. 그러면 이 문제를 어떻게 해결할것일까?


아두이오 우노 기판을 보면 TX와 RX라고 써있는 LED 가 있는데, 하드웨어 시리얼 통신을 할때만 깜빡 거린다.




PC에 연결이 되어 있을 때, AT 명령을 실행할때는 이 LED가 빛나지 않고, 코드를 업로드 할때만 빛이 난다. 즉 코드 업로드 시에만 하드웨어 시리얼을 PC가 사용한다는 것이기 때문에, 코드를 업로드한후에 ESP01 보드를 TX,RX 단자에 연결하면 하드웨어 시리얼을 사용할 수 있다.

아두이노를 이용한 HTTP 통신

아두이노에 ESP01 모듈을 연결했으면, HTTP 통신을 해보도록 한다. ESP01 (ESP8266 계열)은 TCP/UDP 스택이 이미 들어가 있기 때문에, TCP 통신은 가능하다. HTTP 통신을 하려면 TCP 프로토콜을 이용하여 HTTP 메세지를 보내주면 되는데, AT 명령어를 사용하면 된다.

AT 명령어 레퍼런스는 https://www.electrodragon.com/w/ESP8266_AT-Command_firmware 를 참고하기 바란다. AT명령을 이용하여 WIFI 네트워크에 연결하는 방법은 http://bcho.tistory.com/1283 에 설명되어 있다. WIFI 네트워크에 연결되어 있다고 가정하고 AT명령을 이용해서 HTTP 명령을 보내는 방법을 보자


HTTP  서버 연결

HTTP 요청을 보내기 위해서 먼저 웹서버에 TCP 커넥션을 연결해야 한다. TCP 커넥션 연결 명령어는 AT+CIPSTART로 다음과 같다.


AT+CIPSTART={TCP 연결 번호},”TCP”,”웹서버IP”,{포트번호}


이 명령은 해당 웹서버 IP의 포트로 연결을 하고 연결 번호(이름)를  “0”으로 지정한것이다.

210.118.92.89 서버의 80 포트로 연결하려면 Serial 콘솔에서 AT+CIPSTART=0,”TCP”,”210.118.92.89”,80 명령어를 실행하면 된다. 이 연결의 연결 번호는 0 번이 된다.

명령어 전달

서버로 연결이 되었으면, HTTP 명령어를 보내야 하는데, 명령어 전달은 AT+CIPSEND 명령어를 사용하면 된다. 명령어의 형식은 다음과 같다.


AT+CPISEND={TCP 연결번호},{보내고자 하는 메세지 길이}


예를 들어 “GET /index.html” 명령을 0번 TCP 연결로 보내고자 하면, 메세지의 길이가 15자이기 때문에,

AT+CPISEND=0,15 로 실행하고, 그 다음에

> 가 나오면 GET /index.html 을 전송하면 된다.

예제

실제 개발에서는 콘솔에서 AT 명령을 직접 입력해서 사용하는 것이 아니기 때문에, 이를 코드화 해야 하는데, 아래는 코드화 한 예제이다. (실제 개발에서는 사용하지 않을것이기 때문에 테스트는 하지 않았다)

#include <SoftwareSerial.h>


SoftwareSerial esp(2,3); //TX,RX

String SSID="{Wifi SSID}";

String PASSWORD="{Wifi password}";


void connectWifi(){

 String cmd = "AT+CWMODE=1";

 esp.println(cmd);

 cmd ="AT+CWJAP=\""+SSID+"\",\""+PASSWORD+"\"";

 esp.println(cmd);

 delay(1000);

 if(esp.find("OK")){

   Serial.println("Wifi connected");

 }else{

   Serial.println("Cannot connect to Wifi"+esp);

 }

}


void httpGet(String server,String uri){

 String connect_server_cmd = "AI+CIPSTART=4,\"TCP\",\""+server+"\",80";

 esp.println(connect_server_cmd);

 String httpCmd = "GET "+uri;

 String cmd = "AT+CIPSEND=4,"+httpCmd.length();

 esp.println(cmd);

 esp.println(httpCmd);


}

void setup() {

 // put your setup code here, to run once:

 esp.begin(9600);

 Serial.begin(9600);

 connectWifi();

 httpGet("210.192.111.122","/index.html");

}


void loop() {

 // put your main code here, to run repeatedly:


}


위의 붉은 색으로 표시된 부분을 보면 번호 4번으로 TCP연결을 열고, 4번 채널에 AT+CIPSEND 명령을 이용하여 GET /index.html 을 실행하였다.

SDK의 활용

HTTP 통신의 기본 원리를 이해하였으면, 이제 실제로 HTTP 호출을 해보자. AT 명령을 이용해도 되지만,  HTTP 명령어 종류가 많고, 연결, 메세지 전송/받기등 다양한 명령을 AT 명령으로 직접 코딩하기에는 코딩양이 많고 복잡해진다. 그래서 이런 명령을 잘 추상화하여 단순화 해놓은 SDK를 사용하는 것이 좋은데, 아두이노가 오픈 소스인 만큼, 오픈소스 기반의 SDK가 많다. 장점이기도 하지만 반대로 단점도 되는데, 아두이노나 ESP8266 펌웨어의 버전이 낮으면 동작하지 않는 경우도 있고, 또한 많은 라이브러리들이 아두이노 WIFI 실드를 기준으로 개발되었거나 또는 아두이노 → ESP8266으로 통신하는 용도가 아닌 ESP8266 기반의 아두이노 보드를 기준으로 개발된 라이브러리가 많다.

아두이노 라이브러리 추가하기

우리가 사용할 라이브러리는 WiFiESP라는 라이브러리 (https://github.com/bportaluri/WiFiEsp) 로 소프트웨어 시리얼과 하드웨어 시리얼 통신 양쪽을 모두 지원하며, HTTP 형태로 라이브러리가 잘 패키징 되어 있다. 또한 예제가 비교적 잘 정리되어 있어서 사용하기 좋다.

이런 오픈소스 라이브러리를 사용하려면 라이브러리를 설치해야 하는데, 설치 방법은 다음과 같다.

github에 배포된 코드의 경우, 해당 코드를 아래 그림과 같이 download 버튼을 이용하여 zip 파일로 다운로드 받는다.




다음 Sketch에서 Sketch > Include Library > Add .Zip Library 를 선택해서 앞에서 다운 받은 ZIP 파일을 선택하면 라이브러리로 등록되어 사용이 가능하다.




SDK를 이용하여 HTTP 호출


아래코드는 라이브러리를 추가한후에, 예제에 나온 코드를 그대로 사용한 예이다. 아두이노 2,3번 핀을 ESP01의 TX,RX 핀에 연결해서 소프트웨어 시리얼로 통신하였다. 그래서 코드 부분을 보면 SoftwareSerial Serial1(2,3)으로 소프트웨어 시리얼로 선언되어 있는 것을 확인할 수 있다.


#include "WiFiEsp.h"


// Emulate Serial1 on pins 6/7 if not present

#ifndef HAVE_HWSERIAL1

#endif

#include "SoftwareSerial.h"

SoftwareSerial Serial1(2, 3); // RX, TX


char ssid[] = "{WIFI SSID}";            // your network SSID (name)

char pass[] = "{WIFI Password}";        // your network password

int status = WL_IDLE_STATUS;     // the Wifi radio's status


char server[] = "arduino.cc";


// Initialize the Ethernet client object

WiFiEspClient client;


void setup()

{

 // initialize serial for debugging

 Serial.begin(9600);

 // initialize serial for ESP module

 Serial1.begin(9600);

 // initialize ESP module

 WiFi.init(&Serial1);


 // check for the presence of the shield

 if (WiFi.status() == WL_NO_SHIELD) {

   Serial.println("WiFi shield not present");

   // don't continue

   while (true);

 }


 // attempt to connect to WiFi network

 while ( status != WL_CONNECTED) {

   Serial.print("Attempting to connect to WPA SSID: ");

   Serial.println(ssid);

   // Connect to WPA/WPA2 network

   status = WiFi.begin(ssid, pass);

 }


 // you're connected now, so print out the data

 Serial.println("You're connected to the network");

 

 printWifiStatus();


 Serial.println();

 Serial.println("Starting connection to server...");

 // if you get a connection, report back via serial

 if (client.connect(server, 80)) {

   Serial.println("Connected to server");

   // Make a HTTP request

   client.println("GET /asciilogo.txt HTTP/1.1");

   client.println("Host: arduino.cc");

   client.println("Connection: close");

   client.println();

 }

}


void loop()

{

 // if there are incoming bytes available

 // from the server, read them and print them

 while (client.available()) {

   char c = client.read();

   Serial.write(c);

 }


 // if the server's disconnected, stop the client

 if (!client.connected()) {

   Serial.println();

   Serial.println("Disconnecting from server...");

   client.stop();


   // do nothing forevermore

   while (true);

 }

}



void printWifiStatus()

{

 // print the SSID of the network you're attached to

 Serial.print("SSID: ");

 Serial.println(WiFi.SSID());


 // print your WiFi shield's IP address

 IPAddress ip = WiFi.localIP();

 Serial.print("IP Address: ");

 Serial.println(ip);


 // print the received signal strength

 long rssi = WiFi.RSSI();

 Serial.print("Signal strength (RSSI):");

 Serial.print(rssi);

 Serial.println(" dBm");

}


접속하고자하는 서버의 주소는

char server[] = "arduino.cc";

에 arduino.cc 로 정의되어 있고


   client.println("GET /asciilogo.txt HTTP/1.1");

   client.println("Host: arduino.cc");

   client.println("Connection: close");

   client.println();


명령어를 이용해서 arduino.cc/asciilogo.txt 파일을 HTTP GET으로 읽어오게 되어있다.

읽어온 응답값을

 while (client.available()) {

   char c = client.read();

   Serial.write(c);

 }


코드를 이용해서 client.read를 통해서 한글자씩 읽어온후 화면에 출력하기 위해서 Seirial.write(c)를 이용하여 한글자씩 콘솔에 출력하였다.

실행 결과는 다음과 같다.



결론

테스트를 하면서 가장 어려웠던 점이 아두이노 우노에서 ESP01 로 소프트웨어 시리얼 통신을 하는 경우 HTTP나 WIFI SDK를 찾기가 어려웠다. 대부분 ESP8266을 MCU로 하는 보드 (예를 들어 nodeMCU, ESP12,Wemo D1/D1 mini)를 베이스로 하는 경우가 많았는데, 그도 그럴것이 개발이나 테스트 용도가 아니라 실제 디바이스로 만들어서 사용하려면 우노 + ESP01 조합은 크기도 크고 연결도 복잡해서 효용성이 없다. 네트워크 통신을 하는 시나리오등은 IOT와 같이 센서 데이타를 얻어서 서버에 전송하는 케이스이기 때문에,  하나의 소형 디바이스로 처리가 가능하다면 하나의 디바이스를 사용하는 것이 훨씬 효율적이다. (가격이나 배터리 용량면등).

다음 글에서는 실제로 센서에서 데이타를 읽어서 HTTP를 이용해서 서버에 전송하는 부분을 고민해보도록 하겠다.

참고자료



ESP01 (ESP8266) 사용하기

프로그래밍/아두이노 | 2018.09.30 00:00 | Posted by 조대협

아두이노 ESP01 모듈 사용하기

조대협 (http://bcho.tistory.com)

아두이노 WIFI 모듈

아두이노 WIFI 모듈은 여러가지가 있는데, 아두이노용으로 나온 와이파이 실드의 경우에는 가격이 비싸다. (아두이노에서 WIFI 연결하는 방법은 http://bcho.tistory.com/1280 자료 참고) 그래서 가장 범용적으로 사용되는 칩셋은 ESP8266 칩셋인데, ESP8266 칩셋으로 나온 보드는 여러가지가 있다. ESP01~ESP14 모듈등이 있고, 조금더 사용이 편한 모듈로는 nodemcu와 같은 모듈이 있다.




ESP8266 모듈 시리즈 https://en.wikipedia.org/wiki/ESP8266

오늘 다루는 모듈은 이중에서 가장 저렴한 ESP01 모듈이다.

아두이노에서 이모듈을 사용하기에는 몇가지 넘어야할 산이 있다.

전원

ESP 01은 3V로 동작을 하는데, 전류 소모량이 많다. 그래서 아두이노 보드의 3.3V 단자에 연결하게 되면 전력양이 낮아서 오작동하는 경우가 많고 GND와 전원으로 들어가는 선이 많아서, 아래와 같이 배선이 복잡해진다.


<출처 : https://m.blog.naver.com/roboholic84/221261124179>

통신속도와 펌웨어 업그레이드

그리고 가장 난관중 하나가, 아두이노의 시리얼 통신의 경우 9600bps를 사용하는데, 불행하게도 ESP01의 기본 통신속도는 115200bps로 설정되어 있기 때문에 펌웨어 업그레이드를 통해서 디폴트 통신 속도를 9600 bps로 변경해줘야 한다. 이 과정에 여러 배선 설계를 해야 하고 별도의 펌웨어 업그레이드 프로그램을 설치해야하기 때문에 그 과정이 까다롭다.



<그림 USB to ESP01 연결 아답터>

그래서 아두이노 보드를 거치지 않고 바로 PC USB에서 ESP01로 연결을해서 쉽게 펌웨어를 업데이트할 수 있게 해주는 USB to ESP01 아답터가 있기는 하지만 여전히 펌웨어 업데이트가 필요하다.


ESP01 아답터

그래서 검색을 하다 찾은 아답터가 ESP01 아답터 이다.


이렇게 아답터 위에 ESP01 보드를 설치하면 되는 형태이다. (구매 링크)

https://m.blog.naver.com/roboholic84/221261124179 글에 상세한 소개와 설명이 잘 나와 있는데, 따라해보니 ESP01의 펌웨어 자체가 업그레이드 되어 있어서, 명령어가 동작하지 않는 부분이 있어서 몇가지 수정을 하였다. 포스팅 내용을 참고하여 몇가지 부분을 수정하였다.

배선 작업은 아래와 같다.


대략 빵판에 연결하면 다음과 같은 모습이된다.



레큘레이터가 내장되어 있기 때문에 아두이노 5V에 VCC를 연결하면 되고, RX와 TX를 각각 3번과 2번 포트에 연결한다.

다음에 아래와 같은 코드를 작성하여 실행한다.

#include <SoftwareSerial.h>


SoftwareSerial mySerial(2, 3); // RX, TX


void setup() {

 Serial.begin(9600);

 mySerial.begin(115200);

}


void loop() {

 if (mySerial.available()) {

   Serial.write(mySerial.read());

 }

 if (Serial.available()) {

   mySerial.write(Serial.read());

 }

}


위의 예제는 아두이노 콘솔에서 받은 명령을 ESP01 시리얼 포트로 전송하고 ESP01 에서 나온 결과값을 아두이노 콘솔에 출력하도록 하는 코드이다. ESP01의 디폴트 통신속도가 115200이기 때문에 mySerial.begin에서 통신 속도를 115200으로 설정하였다. (추후 바꿀것이다.)


를 실행해서 AT 명령을 실행해보자. 연결이 제대로 되었으면 아래와 그림과 같이 OK 응답이 오는 것을 볼 수 있다.



다음 ESP01 보드의 통신 속도를 변경해보자,원본 문서에는 AT+CIOBAUD=9600 명령으로 변경하도록 되어 있는데, 이 명령은 최신 펌웨어에서는 동작하지 않고 AT+IPR이라는 명령을 사용해야 하는데, 이 명령은 연결되어 있는 동안만 통신 속도를 변경하고 다시 ESP01 디바이스를 뺐다가 다시 끼면 원래 통신속도로 돌아간다.

ESP01의 통신속도를 영구적으로 바꿔줄 수 있는 명령이 있는데, AT_UART_DEF라는 명령을 사용하면 된다. (참고 : https://www.esp8266.com/viewtopic.php?f=13&t=718)

사용법 : AT+ UART_DEF=<baudrate>,<databits>,<stopbits>,<parity>,<flow control>


통신속도를 9600으로 영구적으로 변경하기 위해서는 아래 명령을 수행한다.

AT+UART_DEF=9600,8,1,0,0



통신 속도를 9600으로 변경하였기 때문에 코드상에서도 ESP01로 통신하는 속도를 9600으로 변경해줘야 한다.

 mySerial.begin(115200);

부분을

 mySerial.begin(9600);

으로 변경하여 실행하자


WIFI 연결 테스트

네트워크 모드

ESP8266은 네트워크 연결에 대해 3가지 모드를 제공한다.

  • 1 : Stand alone

  • 2 : AP

  • 3 : AP + Standalone

Stand alone 모드는 클라이언트로 작동하는 모드로 AP에 붙어서 네트워크 통신을 할 수 있다.

AP 모드는 ESP8266이 서버가 되는 모드로 다른 단말이 ESP8266에 Wifi로 연결될 수 있게 한다. 그리고 AP+Stand alone 모드는 서버와 클라이언트를 동시에 지원하는 모드이다. 여기서는 1번 AP+Standalone 모드를 사용하도록 하겠다.

모드 전환은 AT+CWMODE={모드 번호}로 가능하다


WIFI 연결하기

클라이언트 모드로 연결을 하였으면 WIFI에 연결해보자.

WIFI 목록을 찾는 명령은 AT+CWLAP 이다. 명령을 실행하면 아래 그림과 같이 연결 가능한 WIFI 목록이 출력된다.


다음 AT+CWJAP=”SSID”,”비밀번호" 명령을 이용하여 연결하고자 하는 WIFI 네트워크에 연결이 가능하다. 여기서는 U+NetC070 네트워크에 연결해보도록 하겠다

AT+CWJAP="U+NetC070","WIFI비밀번호"


명령을 실행하면 아래와 같이 WIFI에 연결이 된것을 확인할 수 있다.



연결이 완료 되었으면

AT+CIFSR

명령을 이용하면 연결된 IP 주소를 읽어낼 수 있다.


이렇게 연결이 된 상태에서는 아두이노의 전원을 껐다가 켜도 다시 같은 네트워크로 자동으로 붙기 때문에, 연결을 명시적으로 끊으려면

AT+CWQAP

명령을 이용하여 명시적으로 연결을 끊어줘야 한다.

다음글에서는 ESP01 모듈을 이용하여 서버와 HTTP 통신을 하는 방법에 대해서 알아보도록 한다.



서보 모터 제어

프로그래밍/아두이노 | 2018.09.28 00:11 | Posted by 조대협


서보 모터 컨트롤 하기


아두이노에서 컨트롤 할 수 있는 모터중 한 종류인 서보 모터를 제어해봤다.

서보 모터는  (Servo Motor) DC 모터와는 다르게 정밀한 컨트롤이 가능한 모터이다.

정확한 각도나 속도로 회전을 할 수 있다. 


테스트에 사용한 모터는 저렴한 서보 모터를 사용했는데, 그래서 그런지 90도, 180도로 각도를 돌려봐도 정확하게 90도, 180도로 돌지는 않았다.


서보 모터는 종류에 따라서 180도만 회전하는 모터, 360 회전하는 모터등으로 최대 회전 각이 다르다.




이런 모양으로 생겼는데, 붉은 선은 5V, 갈색선은 GND, 오렌지 선이 컨트롤 선이다.

이 선을 아래 그림과 같이 9번 핀에 연결하였다.



그리고 아래와 같은 예제 코드를 사용하였다.


#include<Servo.h>


Servo servo;

int value = 0;

void setup() {

  // put your setup code here, to run once:

  servo.attach(9);

  servo.write(0);

  delay(3000);

  servo.write(90);

  delay(3000);

  servo.write(0);

}


void loop() {

  // put your main code here, to run repeatedly:


  servo.write(value);

  value+=10;

  delay(500);

  if(value > 180)

    value = 0;

    

}


Servo.h 라이브러리를 사용해야 하는데, 이클립스에서는 include 에러가 난다. C/C++ 컴파일러 경로 설정을 변경해야 하는데, 귀찮아서 패스. 그냥 아두이노 IDE를 사용하였다.


servo.write에 각도를 인자를 주면 그 각도로 회전이 된다.






구글 클라우드 해커톤 세션으로 진행한 강의 내용입니다

50분 정도 인데, 짧게 쿠버네티스에 대한 설명과, 마이크로서비스에 대한 설명

그리고 쿠버네티스 에코 시스템인 Spinnaker, Istio, KNative 등에 대해서 설명합니다



아두이노 무선 통신 모듈

프로그래밍/아두이노 | 2018.09.21 00:41 | Posted by 조대협

아두이노 무선 통신 모듈


아두이노를 어느정도 테스트해보니, 서버에 붙여서 몬가를 해봐야겠다는 생각에 와이파이 지원 모듈을 찾다보니 꽤나 복잡해서 정리를 해본다.

아두이노의 무선 통신 모듈은 원래 아두이노 와이파이 실드라는 파츠로, 아두이노 우노 위에 붙여서 사용하는 형태 였는데, 사용방법은 편리하나 가격이 비싼 편이고, 아두이노 우노 시리즈에만 호환되는 단점을 가지고 있다.


<그림 아두이노 와이파이 실드 정품 >


그러다가 WIFI가 대중화된것이 ESP8266 이라는 칩셋인데, 아주 저렴한 가격에 (인터넷에서 2000원 수준) 이를 통해서 많이 대중화가 되었다.

<그림 AI Cloud사의 ESP8266 모듈>

가격은 저렴하지만 3.3V 전류를 사용하기 때문에, 별도의 저항등의 배선 설정이 필요하고, 특히 시리얼 라인 속도도 조정해야 하고, 펌웨어를 업그레이드해야 하는등 번거로운 작업이 필요하다. (특히 나같은 맥북 사용자에게는 쥐약인..)

ESP8266 모듈의 호한 모듈로는 AI Thinker사의 제품이 많은데 ESP-OO 식의 이름을 가지고 있는데, 인터넷 상에서 파는 제품은 ESP-12 시리즈가 많다. ESP-12등은 ESP8266 모듈 호환이라고 생각하면 된다. 


그래서 대체품을 찾은것이 nodemcu 라는 제품인데

<그림 ESP8266 기반의 nodemcu>


ESP 8266 모듈을 기판에 붙여 놓고 입출력 IO핀을 제공하면서도, USB 단자가 있어서 펌웨어 업그레이드를 상대적으로 편하게 할 수 있으며, Lua 스크립트로도 코딩이 가능하다.

즉 아두이노가 없이도 단독적으로 기능 수행이 가능하다는 이야기인데, 크기가 작고 와이파이 기능이 탑재되어 있어서 IOT 기기류로 많이 활용되는 듯하다. 특히 Lua 의 경우 HTTP나 MQTT와 같은 IOT 프로토콜을 손쉽게 호출할 수 있는 라이브러리가 있어서 어떤면에서는 아두이노보다 통신 프로그램에는 유리하지 않은가 싶다. 


ESP8266이 많이 사용되기는 했지만, 다음 버전으로 ESP32 라는 프로세서가 등장하는데, WIFI뿐 아니라 블루투스 통신을 함께 지원한다.

아무래도 후속 기중인 만큼 더 많은 GPIO 포트와, 더 빠른 WIFI통신을 지원하기는 하지만, 가격이 약간 더 비싸다는 단점이 있다. (6$~12$선)


<그림 ESP 32S 칩을 내장한 nodemcu 모듈 >


그외에, 아두이노 호환보드중에는 WIFI 기능을 내장한 보드들이 꽤 많다. 대표적인 보드로는 Wemos 사의 제품이 있는데, ESP8266 을 메인 CPU로 한후에, 우노에 맞는 사이즈와 핀 배열과 기능을 제공하면서 기본적으로 ESP8266 기반의 와이파이 통신을 지원한다. 


또는 아두이노 WIFI 실드 제품을 사용하는 것도 방법이 된다.






'프로그래밍 > 아두이노' 카테고리의 다른 글

ESP01 (ESP8266) 사용하기  (0) 2018.09.30
서보 모터 제어  (0) 2018.09.28
아두이노 무선 통신 모듈  (1) 2018.09.21
아두이노 기울기 센서와 소음 센서  (0) 2018.09.18
아두이노 조도 센서  (0) 2018.09.16
Hello 아두이노  (0) 2018.09.16

아두이노 기울기 센서와 소음 센서


아두이노 기울기와 소음센서를 간단하게 테스트 해봤다.


소음 센서

소음 센서는 소리가 나면 그 값을 아닐로그값으로 바꿔서 준다. +5V와 GND에 연결하고, 데이타값은 아날로그 포트에 연결해서 받는다. 



아래는 간단한 코드

#include <Arduino.h>


void setup() {

Serial.begin(115200);

}


void loop() {

int sound = 1024-analogRead(A0);

Serial.println(sound);

delay(20);

}


--- 9월 18일 수정 ---

위의 센서는 아날로그가 아니라 디지털임.

소리가 날때, 작은 값이 나오고, 소리가 안날때 1024값이 나오는데, 이건 중간에 가변 저항을 돌려보면 반대로 만들 수 있음 (포텐셔미터라고들 부르는데)

"디지탈 센서의 출력을 아날로그로 읽었으니 0과 1024 혹은 그에 가까운 값이 나오는것이 맞고, 그 사이값 30, 600, 900 이런 값들은 디지탈 출력이 빠른 시간에 ON/OFF 되는 것이 마치 PWM 출력처럼 읽혀서 나오는 값입니다." 커뮤니티에서 임성국님이 정리해주신 내용


기울기 센서

각도등은 받을 수 없고, 디지털 센서로 기울어진 여부만 측정한다.



마찬가지로 5V와 GND 단자에 연결한 후에, 데이타 단자를 디지털 단자에 연결하고, 이 단자를 입력값으로 설정하여 기울어진 여부를 입력 받는다.


#include <Arduino.h>


void setup() {

pinMode(13,INPUT);

Serial.begin(115200);


}


void loop() {

boolean tilt = digitalRead(13);

delay(300);

Serial.print(tilt);


}





'프로그래밍 > 아두이노' 카테고리의 다른 글

ESP01 (ESP8266) 사용하기  (0) 2018.09.30
서보 모터 제어  (0) 2018.09.28
아두이노 무선 통신 모듈  (1) 2018.09.21
아두이노 기울기 센서와 소음 센서  (0) 2018.09.18
아두이노 조도 센서  (0) 2018.09.16
Hello 아두이노  (0) 2018.09.16

아두이노 조도 센서

프로그래밍/아두이노 | 2018.09.16 00:59 | Posted by 조대협

가지고 있는 센서가 몇개 안되서, 그중에 간단한 조도 센서를 테스트해봤다.

5V와 아날로그 INPUT 단자를 이용하면, 쉽게 값을 받을 수 있다.




코드는

#include <Arduino.h>


void setup() {

Serial.begin(115200);

}


void loop() {

int light = analogRead(A0);

Serial.println(light);

delay(500);

}


특별한 부분은 없고, analogRead 함수를 써서, 읽고자 하는 아날로그 포트를 지정해주면 된다.

노트북 빼고 연결 가능하게, 외부 전원 설치방법과, WIFI 연결, HTTP REST API 호출, LCD 출력등을 좀 더 테스트 해봐야겠다.

SERVO 모터를 이용해서, 조정하는 것도 해보고. 이걸 하면 로보트팔을 만들 수 있지 않을까?



'프로그래밍 > 아두이노' 카테고리의 다른 글

ESP01 (ESP8266) 사용하기  (0) 2018.09.30
서보 모터 제어  (0) 2018.09.28
아두이노 무선 통신 모듈  (1) 2018.09.21
아두이노 기울기 센서와 소음 센서  (0) 2018.09.18
아두이노 조도 센서  (0) 2018.09.16
Hello 아두이노  (0) 2018.09.16

Hello 아두이노

프로그래밍/아두이노 | 2018.09.16 00:39 | Posted by 조대협


잠깐 아두이노를 만져보고 단상 노트


우연한 기회에 아두이노를 보게 되서, 예전에 딸 학습용으로 사놨던 아두이노 키트를 꺼내서 이리저리 만져보았다.

요즘 아이들이 아두이노로 메이커를 많이 한다고 해서, 난이도가 높지 않을것이라고는 예상 했지만, 직접 해보니, 상당히 심플하다.

개발 환경 설정에서 부터, 코드를 올리는 과정까지 IDE에서 손쉽게 접근이 가능하고, 브래드보드 덕분인지, 회로 구현도 편하고, 부품 수급도 편리하다.

더군다나, 시장에 많이 풀린 덕분인지 컨텐츠도 무궁무진해서, 손쉽게 접근이 가능하다.


13개의 핀을 IN/OUT으로 설정하고 단순하게 신호를 보내거나 받아서 처리하는 구조인데...

저항이나 콘덴서와 같은 전자 부품을 어떻게 다룰지 약간 걱정은 되지만, 아이들도 한다고 하니, 크게 문제는 없을듯 하다.


대략 감도 잡고 환경도 구축을 했는데, 무엇을 해볼까 고민중

아무래도 백앤드 전문이니, WIFI 를 달아서, 센서로 값들을 수집하는 IOT 시나리오를 구축하는 것도 괜찮을거 같고

ML API나 쳇봇 API를 이용해서, 간단한 로봇을 만들거나, 아니면 라즈베리 파이로 넘어가서 텐서플로우 모델을 내려보는것도 괜찮을거 같기는 한데, 어떤 시나리오를 할지 역시 아이디어가 문제다.





아래는 오늘 개발 환경 구축에 참고한 영상인데, 아무래도 이클립스와 같은 툴에 익어 있다 보니, 아두이노 정식 IDE보다는 이클립스가 날거 같아서 설치했는데, 역시 요즘은 글보다는 영상이 대세인가 보다. 이해도 빠르고.. 따라하기도 편리하다. 블로그 대신 이제 영상으로 넘어가야 하나??



'프로그래밍 > 아두이노' 카테고리의 다른 글

ESP01 (ESP8266) 사용하기  (0) 2018.09.30
서보 모터 제어  (0) 2018.09.28
아두이노 무선 통신 모듈  (1) 2018.09.21
아두이노 기울기 센서와 소음 센서  (0) 2018.09.18
아두이노 조도 센서  (0) 2018.09.16
Hello 아두이노  (0) 2018.09.16

쿠버네티스 #19

보안 4/4 - Pod Security Policy

조대협 (http://bcho.tistory.com)



SecurityContext가 컨테이너나 Pod의 보안 기능을 정의 하는 것이라면, Pod Security Policy (이하 PSP)는 보안 기능에 대한 정책을 정의 하는 것이다.

예를 들어, 정책으로 Pod를 생성할때는 반드시 root 사용자를 사용하지 못하도록 강제한다던지, Privileged 모드를 사용못하도록 강제할 수 있다. 현재는 (2018년9월1일) 베타 상태이기 때문에 다소의 기능 변경이 있을 수 있음을 염두하고 사용하도록 하자.

개념

개념이 복잡하기 때문에 먼저 기본적인 개념을 이해한 후에, 각 상세를 살펴보도록 하자.

먼저 아래 그림을 보자 PSP는 생성후에, 사용자에게 지정이 된다.

그리고 Pod를 생성할때, Pod의 보안 요건을 SecurityContext를 이용해서 Pod 설정에 정의한다.

Pod를 생성하려고 할때, 생성자(사용자)의 PSP를 레퍼런스 하는데, Pod의 보안 요건이 사용자에게 정의되어 있는 PSP 요건을 만족하면, Pod가 생성된다.



반대로, Pod를 생성할때, Pod의 보안 요건 (SecurityContext)가 Pod를 생성하고자하는 사용자의 PSP요건을 만족하지 않으면, Pod 생성이 거부된다. 아래 그림은 사용자의 PSP에서 Privileged 모드를 사용할 수 없도록 설정하였으나, Pod를 생성할때 Privileged 모드를 Pod 가 사용할 수 있도록 설정하였기 때문에, Pod를 생성에 실패하는 흐름이다.




Pod Security Policy

Pod Security Policy는 Security Context와 달리 클러스터 리소스 (Cluster Resource)이다.

즉 적용하는 순간 클러스터 전체에 적용이 된다는 이야기이다.


정책 종류

Pod Security Policy를 통해서 통제할 수 있는 정책은 다음과 같다.

(출처 https://kubernetes.io/docs/concepts/policy/pod-security-policy/) 자세한 내용은 원본 출처를 참고하기 바란다.


Control Aspect

Field Names

Running of privileged containers

privileged

Usage of host namespaces

hostPID, hostIPC

Usage of host networking and ports

hostNetwork, hostPorts

Usage of volume types

volumes

Usage of the host filesystem

allowedHostPaths

White list of Flexvolume drivers

allowedFlexVolumes

Allocating an FSGroup that owns the pod’s volumes

fsGroup

Requiring the use of a read only root file system

readOnlyRootFilesystem

The user and group IDs of the container

runAsUser, supplementalGroups

Restricting escalation to root privileges

allowPrivilegeEscalation, defaultAllowPrivilegeEscalation

Linux capabilities

defaultAddCapabilities, requiredDropCapabilities, allowedCapabilities

The SELinux context of the container

seLinux

The AppArmor profile used by containers

annotations

The seccomp profile used by containers

annotations

The sysctl profile used by containers

annotations



포맷

PSP의 포맷을 이해하기 위해서 아래 예제를 보자

apiVersion: extensions/v1beta1

kind: PodSecurityPolicy

metadata:

 name: nonroot-psp

spec:

 seLinux:

   rule: RunAsAny

 supplementalGroups:

   rule: RunAsAny

 runAsUser:

   rule: MustRunAsNonRoot

 fsGroup:

   rule: RunAsAny

 volumes:

 - '*'


nonroot-psp 라는 이름으로 PSP를 정의하였고, seLinux,supplementalGroup,fsGroup과 volumes(디스크)에 대한 권한은 모두 허용하였다. runAsUser에 rule (규칙)을 MustRunAsNonRoot로 지정해서, 이 정책을 적용 받은 사용자는 Pod를 생성할때 Pod가 반드시 root 사용자가 아닌 다른 사용자를 지정하도록 정의했다.

PSP 사용자 적용

PSP 를 정의하고 실행한다고 해도, 실제로 적용되지 않는다. PSP를 적용하기 위해서는 생성한 PSP를 RBAC을 이용하여 ClusterRole을 만들고, 이 ClusterRole을 사용자에게 부여해야 실제로 정책이 적용되기 시작한다. 사용자에게 PSP를 적용하는 부분은 뒤의 예제에서 살펴보자

이때 주의할점은 사용자의 정의인데, 쉽게 생각하면 사용자를 사람으로만 생각할 수 있는데, 쿠버네티스의 사용자는 사람이 될 수 도 있지만 서비스 어카운트 (Service account)가 될 수 도 있다.

쿠버네티스에서 Pod를 생성하는 주체는 사용자가 kubectl 등으로 Pod를 직접생성할 경우, 사람이 사용자가 되지만, 대부분의 경우 Pod의 생성과 관리는 Deployment나 ReplicaSet과 같은 컨트롤러를 이용하기 때문에, 이 경우에는 컨트롤러들이 사용하는 서비스 어카운트가 사용자가 되는 경우가 많다.

그래서, PSP를 적용하는 대상은 일반 사용자가 될 수 도 있지만 서비스 어카운트에 PSP를 적용해야 하는 경우가 많다는 것을 반드시 기억해야 한다.

PSP 활성화

PSP는 쿠버네티스 클러스터에 디폴트로는 비활성화 되어 있다. PSP 기능을 사용하기 위해서는 이를 활성화 해야 하는데, PSP는 admission controller에 의해서 컨트롤 된다.

구글 클라우드

구글 클라우드에서 PSP를 활성화 하는 방법은 아래와 같이 gcloud 명령을 이용하면 된다.


%gcloud beta container clusters update {쿠버네티스 클러스터 이름} --enable-pod-security-policy --zone={클러스터가 생성된 구글 클라우드 존}


만약에 활성화된 PSP 기능을 비활성화 하고 싶으면 아래와 같이 gcloud 에서 --no-enable-pod-security-policy  옵션을 사용하면 된다.


gcloud beta container clusters update {쿠버네티스 클러스터 이름}  --no-enable-pod-security-policy --zone={클러스터가 생성된 구글 클라우드 존}

Minikube

minikube start --extra-config=apiserver.GenericServerRunOptions.AdmissionControl=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds,PodSecurityPolicy


주의할점은 PSP 기능이 활성화된후에, PSP가 적용되지 않은 사용자(사람과, 서비스어카운트 모두)의 경우에는 Pod를 생성할 수 없기 때문에, 기존에 잘 생성되던 Pod가 갑자기 생성되지 않는 경우가 많기 때문에, 반드시 기능을 활성화하기 전에 반드시, 사용자마다 적절한 PSP를 생성해서 적용하기 바란다. (PSP기능을 활성화하지 않더라도 기본적으로 PSP 정의및, PSP를 사용자에게 적용하는 것은 가능하다.)

예제

개념에 대한 이해가 끝났으면 이제 실제 예제를 통해서 어떻게 PSP를 생성 및 적용하는지를 알아보도록 하자. 예제는 다음 순서로 진행하도록 한다.

  1. PSP 정의 : Root 권한을 사용이 불가능한 PSP를 생성한다.

  2. 서비스 어카운트 생성 : PSP를 생성할 서비스 어카운트를 생성한다. Pod를 바로 생성하는 것이 아니라 Deployment를 통해서 생성할것이기 때문에 Deployment에서 이 서비스 어카운트를 사용할것이다.

  3. ClusterRole 생성 : 다음 1에서 만든 PSP를 2에서 만든 서비스 어카운트에 적용하기 위해서, PSP를 가지고 있는 ClusterRole을 생성한다.

  4. ClusterRoleBinding을 이용하여 서비스 어카운트에 PSP 적용 : 3에서 만든 ClusterRole을 2에서 만든 서비스 어카운트에 적용한다.

  5. Admission controller 활성화 : PSP를 사용하기 위해서 Admission controller를 활성화 한다.

  6. Pod 정의 및 생성 : 2에서 만든 서비스 어카운트를 이용하여 Deployment 를 정의한다.

  7. 테스트 : 테스트를 위해서, root user를 사용하는 deployment와, root user를 사용하지 않는 deployment 두개를 각각 생성해서 psp 가 제대로 적용되는지를 확인한다.

PSP 정의

PSP를 정의해보자. 아래와 같이 nonroot-psp.yaml 을 작성한다. 이 PSP는 runAsUser에서 MustRunAsNotRoot 규칙을 추가해서, Root 권한으로 컨테이너가 돌지 않도록 하는 정책이다.


# nonroot-psp.yaml

apiVersion: extensions/v1beta1

kind: PodSecurityPolicy

metadata:

 name: nonroot-psp

spec:

 seLinux:

   rule: RunAsAny

 supplementalGroups:

   rule: RunAsAny

 runAsUser:

   rule: MustRunAsNonRoot

 fsGroup:

   rule: RunAsAny

 volumes:

 - '*'


파일을 nonroot-psp.yaml 파일로 저장한후에,

%kubectl create -f nonroot-psp.yaml

명령어를 이용하여 PSP를 생성한후에,

%kubectl get psp

명령을 이용하여, PSP가 생성된것을 확인하자




서비스 어카운트 생성

서비스 어카운트 생성을 위해서 아래 yaml 파일을 작성하고, 서비스 어카운트를 생성하여 확인하자


#nonroot-sa.yaml

apiVersion: v1

kind: ServiceAccount

metadata:

 name: nonroot-sa



ClusterRole 생성 및 적용

서비스 어카운트를 생성하였으면, 앞에 만든 PSP nonroot-psp 를 사용하는 ClusterRole nonroot-clusterrole을 생성하고, 이 롤을 nonroot-clusterrole-bindings를 이용하여, 앞서 만든 서비스 어카운트 nonroot-sa 에 연결한다.


아래와 같이 ClusterRole을 생성하는데, resouces 타입을 podsecuritypolicies 로 정의하고, 리소스 이름은 앞에서 생성한 PSP인 nonroot-psp로 지정한다. 그리고, 이 psp를 사용하기 위해서 verb는 “use”로 지정한다

#nonroot-clusterbinding.yaml

apiVersion: rbac.authorization.k8s.io/v1

kind: ClusterRole

metadata:

 name: nonroot-clusterrole

rules:

- apiGroups:

 - policy

 resources:

 - podsecuritypolicies

 resourceNames:

 - nonroot-psp

 verbs:

 - use


%kubectl create -f nonroot-clusterrole.yaml

명령어를 이용하여 위의 ClusterRole을 생성한후에, 이 ClusterRole을 서비스 어카운트 nonroot-sa 에 적용하자.

아래와 같이 nonroot-clusterrolebinding.yaml 를 생성한후,


#nonroot-clusterrolebinding.yaml

apiVersion: rbac.authorization.k8s.io/v1

kind: ClusterRoleBinding

metadata:

 name: nonroot-clusterrole-bindings

subjects:

- kind: ServiceAccount

 name: sa-nonroot

 namespace: default

roleRef:

 apiGroup: rbac.authorization.k8s.io

 kind: ClusterRole

 name: nonroot-clusterrole


%kubectl create -f nonroot-clusterrolebinding.yaml

명령어를 이용하여 ClusterRole nonroot-clusterrole을 서비스 어카운트 sa-nonroot에 적용한다.

도커 컨테이너 생성

이제 PSP가 생성되었고, 이 PSP를 사용하는 서비스 어카운트 nonroot-sa 가 완성되었으면, 이를 실제로 배포에 적용해보자. 배포에 앞서서 컨테이너 이미지를 만든다.

아래는 Docker 파일인데, 앞의 보안 컨텍스트 설명때 사용한 컨테이너와 동일하다.


#Dockerfile

FROM node:carbon

EXPOSE 8080

RUN groupadd -r -g 2001 appuser && useradd -r -u 1001 -g appuser appuser

RUN mkdir /home/appuser && chown appuser /home/appuser

USER appuser

WORKDIR /home/appuser

COPY --chown=appuser:appuser server.js .

CMD node server.js > /home/appuser/log.out

생성된 도커이미지를 gcr.io/terrycho-sandbox/nonroot-containe:v1 이름으로 docker push 명령을 이용해서  컨테이너 레지스트리에 등록한다.

PSP 기능 활성화

이미지까지 준비가 되었으면, 이제 Pod를 생성할 모든 준비가 되었는데, PSP를 사용하려면, 쿠버네티스 클러스터에서 PSP 기능을 활성화 해야 한다.

다음 명령어를 이용해서 PSP를 활성화한다.

%gcloud beta container clusters update {쿠버네티스 클러스터 이름} --enable-pod-security-policy --zone={클러스터가 생성된 구글 클라우드 존}


아래 그림과 같이 PSP 기능이 활성화 되는 것을 확인한다.


Deployment 생성

기능 활성화가 끝났으면, 이제 Pod를 deploy해보자.

아래는 nonroot-deploy.yaml 파일이다.


#nonroot-deploy.yaml

apiVersion: apps/v1

kind: Deployment

metadata:

 name: nonroot-deploy

spec:

 replicas: 3

 selector:

   matchLabels:

     app: nonroot

 template:

   metadata:

     name: nonroot-pod

     labels:

       app: nonroot

   spec:

     serviceAccountName: nonroot-sa

     securityContext:

       runAsUser: 1001

       fsGroup: 2001

     containers:

     - name: nonroot

       image: gcr.io/terrycho-sandbox/security-context:v1

       imagePullPolicy: Always

       ports:

       - containerPort: 8080


우리가 nonroot-psp를 사용하기 위해서, 이 psp가 정의된 서비스 어카운트 nonroot-sa를 사용하도록 하였다. 그래고 nonroot-psp에 정의한데로, 컨테이너가 root 권한으로 돌지 않도록 securityContext에 사용자 ID를 1001번으로 지정하였다.

%kubectl create -f nonroot-deploy.yaml

을 실행한후,

%kubectl get deploy 명령어를 실행해보면 아래와 같이 3개의 Pod가 생성된것을 확인할 수 있다.


보안 정책에 위배되는 Deployment 생성

이번에는 PSP 위반으로, Pod 가 생성되지 않는 테스트를 해보자.

아래와 같이 root-deploy.yaml 이라는 이름으로, Deployment 스크립트를 작성하자.


#root-deploy.yaml

apiVersion: apps/v1

kind: Deployment

metadata:

 name: root-deploy

spec:

 replicas: 3

 selector:

   matchLabels:

     app: root

 template:

   metadata:

     name: root-pod

     labels:

       app: root

   spec:

     serviceAccountName: nonroot-sa

     containers:

     - name: root

       image: gcr.io/terrycho-sandbox/nonroot-containe:v1

       imagePullPolicy: Always

       ports:

       - containerPort: 8080


이 스크립트는 앞에서 작성한 nonroot-deploy.yaml 과 거의 유사하지만 Security Context에서 사용자 ID를 지정하는 부분이 없기 때문에, 디폴트로 root로 컨테이너가 기동된다. 그래서 PSP에 위반되게된다.


%kubectl create -f root-deploy.yaml

을 실행하면 결과가 아래와 같다.



맨 아래 root-deploy-7895f57f4를 보면, Current 가 0으로 Pod가 하나도 기동되지 않았음을 확인할 수 있다.

원인을 파악하기 위해서 Pod를 만드는 ReplicaSet을 찾아보자

%kubectl get rs

명령을 아래와 같이 ReplicaSet 리스트를 얻을 수 있다.

%kubectl describe rs root-deploy-7895f57f4

명령을 실행해서 ReplicaSet의 디테일과 로그를 확인해보면 다음과 같다.



그림과 같이 Pod 생성이 정책 위반으로 인해서 실패한것을 확인할 수 있다.



쿠버네티스 #18

보안 3/4 - Security Context

조대협 (http://bcho.tistory.com)

보안 컨택스트

보안 컨택스트 (Security context)는 쿠버네티스의 Pod나 컨테이너에 대한 접근 제어 설정(Access Control Setting)이나, 특수 권한 (Privilege)를 설정하는 기능을 제공한다. 단어가 추상적이기 때문에 바로 이해하기 약간 어려울 수 있는데, 몇가지 예를 들어보면, 컨테이너 내에서 동작하는 프로세스의 사용자 ID (UID)나, 그룹 ID (GID)를 설정하거나, 프로세스에 커널에 대한 접근 권한을 부여하는 것과 같은 기능을 수행할 수 있다.


구체적으로 보안 컨택스트가 지원하는 기능은 다음과 같다. 예제와 병행해서 살펴보도록 하자

예제에 사용된 코드는 https://github.com/bwcho75/kube101/tree/master/10.security/4.securityContext 에 있다.

프로세스 사용자 ID와 그룹 ID 지정

Pod나 컨테이너에서 구동되는 프로세스의 사용자 ID와 그룹 ID를 지정한다.

디폴트로 컨테이너에서 구동되는 모든 프로세스는 root 권한으로 실행이 된다. 이 경우 컨테이너 이미지가 오염되어, 악성적인 코드를 가지고 있을 경우에는 root 권한으로 컨테이너의 모든 기능을 장악할 수 있기 때문에, 이를 방지하기 위해서는 컨테이너 내에서 구동되는 사용자 애플리케이션 프로세스의 사용자 ID와 그룹 ID를 지정하여, 특정 자원 (파일이나 디렉토리)에 대한 액세스만을 허용하게 할 필요가 있다.

또한 프로세스의 사용자 ID와 그룹 ID를 지정하면, 생성되는 파일 역시 지정된 사용자 ID와 그룹 ID 를 통해서 생성된다.


간단한 예제를 하나 보자.  우리가 계속 사용해왔던 server.js 로 node.js 서버를 하나 올리는 예제이다.

이 예제를 변경하여, 사용자 ID를 1000으로, 그리고 그룹 ID를 1000으로 지정해서 Pod를 올려보도록 하자.


몇가지 수정이 필요한데, 먼저 기존에 아래와 같이 사용했던 Dockerfile을

FROM node:carbon

EXPOSE 8080

COPY server.js .

CMD node server.js > log.out


log.out 파일 경로를 /log.out에서 아래와 같이 /home/node/log.out 으로 변경한다.

기존의 예제들의 경우에는 컨테이너와 Pod를 root 권한으로 수행했지만 이 예제에서는 runAs를 이용하여 사용자 ID가 1000인 사용자로 돌리기 때문에, 루트 (“/”) 디렉토리에 파일을 생성하려면 권한 에러가 난다.

사용자 ID 1000은 node:carbon 이미지에서 정의되어 있는 node 라는 사용자로, 디폴트로 /home/node 라는 사용자 디렉토리를 가지고 있기 때문에, 이 디렉토리에 파일을 쓰도록 아래와 같이 변경한다.


FROM node:carbon

EXPOSE 8080

COPY server.js .

CMD node server.js > /home/node/log.out


다음 yaml 파일을 작성한다. (1.runas.yaml)

apiVersion: v1

kind: Pod

metadata:

 name: runas

spec:

 securityContext:

   runAsUser: 1000

   fsGroup: 1000

 volumes:

 - name: mydisk

   emptyDir: {}

 containers:

 - name: runas

   image: gcr.io/terrycho-sandbox/security-context:v1

   imagePullPolicy: Always

   volumeMounts:

   - name: mydisk

     mountPath: /mydisk

   ports:

   - containerPort: 8080


위와 같이 securityContext에 runAsUser에 사용자 ID 1000을 그리고 fsGroup에 그룹 ID 1000을 지정하여 Pod를 생성한다. 그리고, mydisk 디스크 볼륨을 생성하여, /mydisk 디렉토리에 마운트 하였다.

생성후 결과를 보자.


생성된 Pod에

%kubectl exec -it runas /bin/bash

명령을 이용하여 로그인 한후 다음 그림과 같이 권한을 체크해본다.




먼저 ps -ef로 생성된 컨테이너들의 사용자 ID를 보면 위와 같이 node (사용자 ID가 1000임)으로 생성되어 있는것을 볼 수 있다.

다음 ls -al /home/node 디렉토리를 보면 컨테이너 생성시 지정한 로그 파일이 생성이 되었고 마찬가지로 사용자 ID와 그룹 ID가 node로 지정된것을 확인할 수 있다.

다음 마운트된 디스크의 디렉토리인 /mydisk 에 myfile이란 파일을 생성해도 파일의 사용자 ID와 그룹 ID가 node로 설정되는것을 확인할 수 있다.


앞의 예제의 경우에는 사용자를 이미지에서 미리 정해진 사용자(node)를 사용하였는데, 만약에 미리 정해진 사용자가 없다면 어떻게 해야 할까?

여러가지 방법이 있겠지만, 도커 이미지를 생성하는 단계에서 사용자를 생성하면 된다. 아래는 사용자를 생성하는 도커 파일 예제이다.


FROM node:carbon

EXPOSE 8080

RUN groupadd -r -g 2001 appuser && useradd -r -u 1001 -g appuser appuser

RUN mkdir /home/appuser && chown appuser /home/appuser

USER appuser

WORKDIR /home/appuser

COPY --chown=appuser:appuser server.js .

CMD node server.js > /home/appuser/log.out


RUN 명령을 이용하여 useradd와 groupadd로 사용자를 생성하고, mkdir로 사용자 홈디렉토리 생성을 한후, 해당 디렉토리의 사용자를 생성한 사용자로 변경한다

그 후에 명령을 실행하기 위해서 명령어를 실행하는 사용자를 변경해야 하는데, USER 명령을 이용하면 사용자를 변경할 수 있다. 이후 부터 생성되는 사용자는 USER에 의해서 지정된 사용자로 실행이 된다.

그 다음은 디렉토리를 WORKDIR을 이용해서 홈디렉토리로 들어가서 COPY와 CMD 명령을 순차로 실행한다.


실행 결과 디렉토리를 확인해보면



와 같이 모든 파일이 앞에서 생성한 appuser 라는 사용자 ID로 생성이 되어 있고, 그룹 역시 appuser로 지정되어 있는 것을 확인할 수 있다.


프로세스를 확인해보면 아래와 같이 앞에서 생성한 appuser라는 사용자로 프로세스가 기동됨을 확인할 수 있다.




SecurityContext for Pod & Container

보안 컨택스트의 적용 범위는 Pod 에 적용해서 Pod 전체 컨테이너에 적용되게 할 수 도 있고, 개발 컨테이너만 적용하게 할 수 도 있다.


아래 예제의 경우에는 컨테이너에 보안 컨택스트를 적용한 예이고,

pods/security/security-context-4.yaml  

apiVersion: v1

kind: Pod

metadata:

 name: security-context-demo-4

spec:

 containers:

 - name: sec-ctx-4

   image: gcr.io/google-samples/node-hello:1.0

   securityContext:

     capabilities:

       add: ["NET_ADMIN", "SYS_TIME"]


아래 예제는 Pod 전체의 컨테이너에 적용한 예이다.

apiVersion: v1

kind: Pod

metadata:

 name: runas

spec:

 securityContext:

   runAsUser: 1000

   fsGroup: 1000

 volumes:

 - name: mydisk

   emptyDir: {}

 containers:

 - name: runas

   image: gcr.io/terrycho-sandbox/security-context:v1

   imagePullPolicy: Always

   volumeMounts:

   - name: mydisk

         : (중략)


노드 커널에 대한 억세스 권한을 제어

쿠버네티스에서 동작하는 컨테이너는 호스트 OS와 가상적으로 분리된 상태에서 기동된다. 그래서, 호스트 커널에 대한 접근이 제한이 된다. 예를 들어 물리머신에 붙어 있는 디바이스를 접근하거나 또는 네트워크 인터페이스에 대한 모든 권한을 원하거나 호스트 머신의 시스템 타임을 바꾸는 것과 같이 호스트 머신에 대한 직접 억세스가 필요한 경우가 있는데, 컨테이너는 이런 기능에 대한 접근을 막고 있다. 그래서 쿠버네티스는 이런 기능에 대한 접근을 허용하기 위해서 privilege 모드라는 것을 가지고 있다.

도커 컨테이너에 대한 privilege 모드 권한은 https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities 문서를 참고하기 바란다.

NFS 디스크 마운트나 FUSE를 이용한 디스크 마운트나, 로우 레벨 네트워크 모니터링이나 통제가 필요할때 사용할 수 있다.

privilege 모드를 true로 하면 커널의 모든 권한을 사용할 수 있는데,

아래 예제는 https://github.com/kubernetes/examples/blob/master/staging/volumes/nfs/nfs-server-rc.yaml 의 일부로 NFS 볼륨을 마운트 하는 리소스 컨트롤러 설정의 일부이다. NFS로 마운트를 하기 위해서 컨테이너의 privileged: true 로 설정하여 privileged 모드로 컨테이너가 실행하게 한것을 확인할 수 있다.


containers:

- name: nfs-server

 image: k8s.gcr.io/volume-nfs:0.8

 ports:

   - name: nfs

     containerPort: 2049

   - name: mountd

     containerPort: 20048

   - name: rpcbind

     containerPort: 111

 securityContext:

   privileged: true


Privileged 모드가 커널의 모든 기능을 부여한다면, 꼭 필요한 기능만 부여할 수 있게 세밀한 컨트롤이 가능하다. 이를 위해서 Linux capability 라는 기능이 있는데, 이 기능을 이용하면 커널의 기능을 선별적으로 허용할 수 있다. 자세한 설명은 https://linux-audit.com/linux-capabilities-hardening-linux-binaries-by-removing-setuid/ 문서를 참고하기 바란다.


아래 예제는 https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-capabilities-for-a-container 중의 일부로, 컨테이너 생성시에, NET_ADMIN과 SYS_TIME 권한 만을 컨테이너에 부여한 내용이다.


pods/security/security-context-4.yaml  

apiVersion: v1

kind: Pod

metadata:

 name: security-context-demo-4

spec:

 containers:

 - name: sec-ctx-4

   image: gcr.io/google-samples/node-hello:1.0

   securityContext:

     capabilities:

       add: ["NET_ADMIN", "SYS_TIME"]

기타

Security context는 이 이외에도 다양한 보안 관련 기능과 리소스에 대한 접근 제어가 가능하다.


  • AppAmor
    는 리눅스 커널의 기능중의 하나로 애플리케이션의 리소스에 대한 접근 권한을 프로필안에 정의하여 적용함으로써, 애플리케이션이 시스템에 접근할 수 있는 권한을 명시적으로 정의 및 제한 할 수 있다.
    (https://wiki.ubuntu.com/AppArmor)

  • Seccomp
    Security computing mode 의 약자로, 애플리케이션의 프로세스가 사용할 수 있는 시스템 콜의 종류를 제한할 수 있다. (https://en.wikipedia.org/wiki/Seccomp)

SELinux
보안 리눅스 기능으로 AppAmor와 유사한 기능을 제공하는데, 리소스에 대한 접근 권한을 정책으로 정의해서 제공한다.


쿠버네티스 #17

보안 2/4 - 네트워크 정책

조대협 (http://bcho.tistory.com)

네트워크 정책 (Network Policy)

쿠버네티스의 보안 기능중의 하나가 네트워크 정책을 정의함으로써 Pod로 부터 들어오거나 나가는 트래픽을 통제할 수 있다. Network Policy라는 기능인데, 일종의 Pod용 방화벽정도의 개념으로 이해하면 된다.

특정 IP나 포트로 부터만 트래픽이 들어오게 하거나 반대로, 특정 IP나 포트로만 트래픽을 내보내게할 수 있는 등의 설정이 가능한데, 이 외에도 다음과 같은 방법으로 Pod에 대한 Network Policy를 설정할 수 있다.

Ingress 트래픽 컨트롤 정의

어디서 들어오는 트래픽을 허용할것인지를 정의하는 방법은 여러가지가 있다.

  • ipBlock
    CIDR IP 대역으로, 특정 IP 대역에서만 트래픽이 들어오도록 지정할 수 있다.

  • podSelector
    label을 이용하여, 특정 label을 가지고 있는 Pod들에서 들어오는 트래픽만 받을 수 있다. 예를 들어 DB Pod의 경우에는 apiserver 로 부터 들어오는 트래픽만 받는것과 같은 정책 정의가 가능하다.

  • namespaceSelector
    재미있는 기능중 하나인데, 특정 namespace로 부터 들어오는 트래픽만을 받을 수 있다. 운영 로깅 서버의 경우에는 운영 환경 namespace에서만 들어오는 트래픽을 받거나, 특정 서비스 컴포넌트의 namespace에서의 트래픽만 들어오게 컨트롤이 가능하다. 내부적으로 새로운 서비스 컴포넌트를 오픈했을때, 베타 서비스를 위해서 특정 서비스나 팀에게만 서비스를 오픈하고자 할때 유용하게 사용할 수 있다.

  • Protocol & Port
    받을 수 있는 프로토콜과 허용되는 포트를 정의할 수 있다.

Egress 트래픽 컨트롤 정의

Egress 트래픽 컨트롤은 ipBlock과 Protocol & Port 두가지만을 지원한다.

  • ipBlock
    트래픽이 나갈 수 있는 IP 대역을 정의한다. 지정된 IP 대역으로만 outbound 호출을할 수 있다.

  • Protocol & Port
    트래픽을 내보낼 수 있는 프로토콜과, 포트를 정의한다.

예제

예제를 살펴보자. 아래 네트워크 정책은 app:apiserver 라는 라벨을 가지고 있는 Pod들의 ingress 네트워크 정책을 정의하는 설정파일로, 5000번 포트만을 통해서 트래픽을 받을 수 있으며, role:monitoring이라는 라벨을 가지고 있는 Pod에서 들어오는 트래픽만 허용한다.


kind: NetworkPolicy

apiVersion: networking.k8s.io/v1

metadata:

 name: api-allow-5000

spec:

 podSelector:

   matchLabels:

     app: apiserver

 ingress:

 - ports:

   - port: 5000

   from:

   - podSelector:

       matchLabels:

         role: monitoring




네트워크 정책을 정의하기 위한 전체 스키마는 다음과 같다.

apiVersion: networking.k8s.io/v1

kind: NetworkPolicy

metadata:

 name: test-network-policy

 namespace: default

spec:

 podSelector:

   matchLabels:

     role: db

 policyTypes:

 - Ingress

 - Egress

 ingress:

 - from:

   - ipBlock:

       cidr: 172.17.0.0/16

       except:

       - 172.17.1.0/24

   - namespaceSelector:

       matchLabels:

         project: myproject

   - podSelector:

       matchLabels:

         role: frontend

   ports:

   - protocol: TCP

     port: 6379

 egress:

 - to:

   - ipBlock:

       cidr: 10.0.0.0/24

   ports:

   - protocol: TCP

     port: 5978


자 그럼, 간단하게 네트워크 정책을 정의해서 적용하는 테스트를 해보자

app:shell 이라는 라벨을 가지는 pod와 app:apiserver 라는 라벨을 가지는 pod 를 만든후에, app:shell pod에서 app:apiserver pod로 HTTP 호출을 하는 것을 테스트 한다.

다음 app:apiserver pod에 label이 app:loadbalancer 인 Pod만 호출을 받을 수 있도록 네트워크 정책을 적용한 후에, app:shell pod에서 app:apiserver로 호출이 되지 않는 것을 확인해보도록 하겠다.


테스트 환경은 구글 클라우드 쿠버네티스 엔진 ( GKE : Google cloud Kubernetes engine) 를 사용하였다.

GKE의 경우에는 NetworkPolicy가 Default로 Disable 상태이기 때문에, GKE 클러스터를 만들때 또는 만든 후에, 이 기능을 Enabled 로 활성화 해줘야 한다.

아래는 GKE 클러스터 생성시, 이 기능을 활성화 하는 부분이다.


클러스터 설정이 끝났으면, 이제 테스트에 사용할 Pod 를 준비해보자.

apiserver는 아래와 같이 server.js 의 node.js 파일을 가지고 8080 포트를 통해서 서비스하는 pod가 된다.

var os = require('os');


var http = require('http');

var handleRequest = function(request, response) {

 response.writeHead(200);

 response.end("Hello World! I'm API Server  "+os.hostname() +" \n");


 //log

 console.log("["+

Date(Date.now()).toLocaleString()+

"] "+os.hostname());

}

var www = http.createServer(handleRequest);

www.listen(8080);

이 서버로 컨테이너 이미지를 만들어서 등록한후에, 그 이미지로 아래와 같이 app:apiserver 라벨을 가지는

Pod를 생성해보자.


apiVersion: v1

kind: Pod

metadata:

 name: apiserver

 labels:

   app: apiserver

spec:

 containers:

 - name: apiserver

   image: gcr.io/terrycho-sandbox/apiserver:v1

   ports:

   - containerPort: 8080

마찬가지로, app:shell 라벨을 가진 Pod도 같은  server.js 파일로 생성한다.

app:apiserver와 app:shell 라벨을 가진 pod를 생성하기 위한 코드와 yaml 파일은 https://github.com/bwcho75/kube101/tree/master/10.security/3.%20networkpolicy 를 참고하기 바란다.


두 개의 Pod를 생성하였으면 shell pod 에 kubectl exec -it {shell pod 명} -- /bin/bash를 이용해서 로그인한후에

apiserver의 URL인 10.20.3.4:8080으로 curl 로 요청을 보내보면 아래와 같이 호출되는 것을 확인할 수 있다.



이번에는 네트워크 정책을 정의하여, app:apiserver pod에 대해서 app:secure-shell 라벨을 가진 pod로 부터만 접근이 가능하도록 정책을 정해서 정의해보자


아래는 네트워크 정책을 정의한 accept-secureshell.yaml 파일이다.

kind: NetworkPolicy

apiVersion: networking.k8s.io/v1

metadata:

 name: accept-secureshell

spec:

 policyTypes:

 - Ingress

 podSelector:

   matchLabels:

     app: apiserver

 ingress:

 - from:

   - podSelector:

       matchLabels:

         app: secureshell


이 설덩은 app:apiserver 라벨이 설정된 Pod로의 트래픽은 라벨이 app:secureshell에서 보내는 트래픽만 받도록 설정한 정책이다.

%kubectl create -f accept-secureshell.yaml

명령어를 이용해서 앞에서 만든 정책을 적용한후에, 앞에서와 같이 app:shell → app:apiserver로 curl 호출을 실행하면 다음과 같이 연결이 막히는 것을 확인할 수 있다.



이외에도 다양한 정책으로, 트래픽을 컨트롤할 수 있는데, 이에 대한 레시피는 https://github.com/ahmetb/kubernetes-network-policy-recipes 문서를 참고하면 좋다.


쿠버네티스 #16


보안 1/4 - 사용자 계정 인증 및 권한 인가

조대협 (http://bcho.tistory.com)


이번글 부터는 몇회에 걸쳐 쿠버네티스 계쩡 인증,인가, 네트워크등 보안에 관련된 부분을 알아보도록 하겠다.

모든 시스템이 그렇듯이, 쿠버네티스 역시 보안이 매우 중요하다. 쿠버네티스는 보안에 관련된 여러가지 기능을 제공하는데, 각각에 대해서 살펴 보도록 하자

사용자 인증 및 권한 관리

인증과 인가 (Authentication & Authorization)

먼저 인증과 인가에 대한 개념에 대해서 이해 하자



인증(Authentication)은 사용자가 누구인지를 식별하는 것이다. 흔히 생각하는 사용자 로그인을 생각하면 된다.  인가는 인증된 사용자가 해당 기능을 실행할 수 있는 권한이 있는지를 체크하는 기능이다.

인증 (Authentication)

쿠버네티스는 계정 체계를 관리함에 있어서 사람이 사용하는 사용자 어카운트와, 시스템이 사용하는 서비스 어카운트 두가지 개념을 제공한다.

사용자 어카운트

사용자 어카운트는 우리가 일반적으로 생각하는 사용자 아이디의 개념이다.

쿠버네티스는 자체적으로 사용자 계정을 관리하고 이를 인증(Authenticate)하는 시스템을 가지고 있지 않다. 반드시 별도의 외부 계정 시스템을 사용해야 하며, 계정 시스템 연동을 위해서 OAuth나 Webhook가 같은 계정 연동 방식을 지원한다.

서비스 어카운트

서비스 어카운트가 다소 낮설 수 있는데, 예를 들어 클라이언트가 쿠버네티스 API를 호출하거나, 콘솔이나 기타 클라이언트가 쿠버네티스 API를 접근하고자 할때, 이는 실제 사람인 사용자가 아니라 시스템이 된다. 그래서, 쿠버네티스에서는 이를 일반 사용자와 분리해서 관리하는데 이를 서비스 어카운트 (service account)라고 한다.

서비스 어카운트를 생성하는 방법은 간단하다.

%kubectl create sa {서비스 어카운트명}

을 실행하면 된다. 아래는 foo 라는 이름으로 서비스 어카운트를 생성하는 예이다.



인증 방법

그러면 계정이 있을때, 이 계정을 이용해서 쿠버네티스의 API에 어떻게 접근을 할까? 쿠버네티스는 사용자 인증을 위해서 여러가지 메커니즘을 제공한다.

용도에 따라서 다양한 인증 방식을 제공한다.


  • Basic HTTP Auth

  • Access token via HTTP Header

  • Client cert

  • Custom made


Basic HTTP Auth는 HTTP 요청에 사용자 아이디와 비밀번호를 실어 보내서 인증하는 방식인데, 아이디와 비밀번호가 네트워크를 통해서 매번 전송되기 때문에 그다지 권장하지 않는 방법이다.

Access token via HTTP Header는 일반적인 REST API 인증에 많이 사용되는 방식인데, 사용자 인증 후에, 사용자에 부여된 API TOKEN을 HTTP Header에 실어서 보내는 방식이다.

Client cert는 클라이언트의 식별을 인증서 (Certification)을 이용해서 인증하는 방식이다. 한국으로 보자면 인터넷 뱅킹의 공인 인증서와 같은 방식으로 생각하면 된다. 보안성은 가장 높으나, 인증서 관리에 추가적인 노력이 필요하다.

그러면 쉽지만 유용하게 사용할 수 있는 Bearer token 방식의 인증 방식을 살펴보도록 하자

Bearer token을 사용하는 방법

인증 메커니즘 중에서 상대적으로 가장 간단한 방법은 API 토큰을 HTTP Header에 넣는 Bearer token 인증 방식이 있다.

서비스 어카운트의 경우에는 인증 토큰 정보를 secret에 저장을 한다. 이 토큰 문자열을 가지고, HTTP 헤더에 “Authorization: Bearer {토큰문자열}로 넣고 호출하면 이 토큰을 이용해서 쿠버네티스는 API 호출에 대한 인증을 수행한다.


서비스 어카운트에서 토큰 문자열을 가지고 오는 방법은

%kubectl describe sa {service account 이름}

을 실행하면 아래와 같이 Token 항목에 토큰을 저장하고 있는 secret 이름이 나온다.


위의 그림에서는 foo-token-zvnzz 이다. 이 이름으로 secret을 조회해보면,

%kubectl describe secret {시크릿명}

명령을 실행하면 아래와 같이 token이라는 항목에, 토큰이 문자열로 출력이 된다.



이 토큰을 HTTP Header 에 “Authorization: Bearer {토큰문자열}” 식으로 넣고 호출하면 된다.


간단한 스크립트를 통해서 API를 호출하는 것을 테스트 해보자

% APISERVER=$(kubectl config view | grep server | cut -f 2- -d ":" | tr -d " ")
명령을 수행하면 환경 변수 APISERVER에 현재 쿠버네티스 클러스터의 API SERVER IP가 저장된다.


다음 APISERVER의 주소를 알았으니

% curl $APISERVER/api

명령을 이용해서 HTTP GET의 /api를 호출해보자. 호출을 하면 SSL 인증서에 대한 인증 에러가 발생한다.


이는 API를 호출할때 인증에 필요한 정보를 기재하지 않았기 때문에, 디폴트로 Client cert를 이용한 인증을 시도하게 되고, 인증서를 지정하지 않았기 때문에 에러가 나게 되는것이다.


그러면 인증 정보를 제대로 지정하기 위해서 서비스 어카운트 default의 토큰을 얻어서 호출해보도록 하자.

다음 스크립트는 서비스 어카운트 default의 secret에서 토큰을 추출해서 저장하는 스크립트이다.

%TOKEN=$(kubectl describe secret $(kubectl get secrets | grep default | cut -f1 -d ' ') | grep -E '^token' | cut -f2 -d':' | tr -d '\t')

스크립트를 실행한후 TOKEN의 내용을 찍어 보면 아래와 같이 API TOKEN이 저장된것을 확인할 수 있다.




다음 이 토큰을 이용해서 API를 호출하면 된다.

%curl https://35.189.143.107/api --header "Authorization: Bearer $TOKEN" --insecure




Kubectl proxy를 이용한 API 호출

앞에서는 HTTP Header에 토큰을 직접 입력하는 방식을 사용했지만, 이렇게 사용하는 경우는 드물다. curl을 이용해서 호출할 경우에는 kubectl proxy 명령어를 이용해서 proxy를 설정하고 proxy로 API URL을 호출하면, 자동으로 이 Proxy가 현재 클라이언트의 kubeconfig file에 저장되어 있는 Credential (인증 정보)를 채워서 자동으로 보내준다.


%kubectl proxy --port=8080

을 실행하게 되면, localhost:8080을 프록시로 하여 쿠버네티스 API서버로 요청을 자동으로 포워딩 해준다.


그리고 curl localhost:8080/api 를 호출하면 {쿠버네티스 API Server}/api 를 호출해주게 된다.




SDK를 이용한 호출

일반적으로 간단한 테스트가 아닌 이상, curl 을 이용해서 직접 API를 호출하는 경우는 드물고, SDK를 사용하게 된다.  쿠버네티스에는 Go/Python/Java/Javascript 등 다양한 프로그래밍 언어를 지원하는 SDK가 있다.

https://kubernetes.io/docs/reference/using-api/client-libraries/#officially-supported-kubernetes-client-libraries


이들 SDK 역시, kubectl proxy 처럼, 로컬의 kubeconfig file의 Credential 정보를 이용해서 API를 인증하고 호출 한다.

권한 관리 (Authorization)

계정 체계와 인증에 대한 이해가 끝났으면, 이번에는 계정 권한에 대해서 알아보자. 쿠버네티스의 권한 처리 체계는 기본적으로 역할기반의 권한 인가 체계를 가지고 있다. 이를 RBAC (Role based access control)이라고 한다.


권한 구조를 도식화 해보면 다음과 같이 표현할 수 있다.


  • 사용자의 계정은 개개별 사용자인 user, 그리고 그 사용자들의 그룹은 user group, 마지막으로 시스템의 계정을 정의하는 service account로 정의된다.

  • 권한은 Role이라는 개념으로 정의가 되는데, 이 Role에는 각각의 리소스에 대한 권한이 정의된다. 예를 들어 pod 정보에대한 create/list/delete등을 정의할 수 있다. 이렇게

  • 이렇게 정의된 Role은 계정과 RoleBinding 이라는 정의를 통해서, 계정과 연결이 된다.


예제를 살펴보자, 아래는 Role을 정의한 yaml 파일이다.

pod-reader라는 Role을 정의하였고, pods에 대한 get/watch/list를 실행할 수 있는 권한을 정의하였다.



다음 이 Role을 사용자에게 부여하기 위해서 RoleBinding 설정을 아래와 같이 정의하자.

아래 Role-Binding은 read-pods라는 이름으로 jane이라는 user에서 Role을 연결하였고, 앞에서 정의한 pod-reader를 연결하도록 정의하였다.




이 예제를 그림으로 표현하면 다음과 같다.



Role vs ClusterRole

Role은 적용 범위에 따라 Cluster Role과 일반 Role로 분리 된다.

Role의 경우 특정 네임스페이스내의 리소스에 대한 권한을 정의할 수 있다.

반면 ClusterRole의 경우, Cluster 전체에 걸쳐서 권한을 정의할 수 있다는 차이가 있다.

또한 ClusterRole의 경우에는 여러 네임스페이스에 걸쳐 있는 nodes 와 같은 리소스스나 /heathz와 같이 리소스 타입이 아닌 자원에 대해서도 권한을 정의할 수 있다.

Role과 ClusterRole은 각각 RoleBinding과 ClusterRoleBinding 을 통해서 사용자에게 적용된다.

Predefined Role

쿠버네티스에는 편의를 위해서 미리 정해진 롤이 있다.


Default ClusterRole

Default ClusterRoleBinding

Description

cluster-admin

system:masters group

쿠버네티스 클러스터에 대해서 수퍼사용자 권한을 부여한다.
ClusterRoleBinding을 이용해서 롤을 연결할 경우에는 모든 네임스페이스와 모든 리소스에 대한 권한을 부여한다. RoleBinding을 이용하여 롤을 부여하는 경우에는 해당 네임 스페이스에 있는 리소스에 대한 모든 컨트롤 권한을 부여한다.

admin

None

관리자 권한의 억세스를제공한다. RoleBinding을 이용한 경우에는 해당 네임스페이스에 대한 대부분의 리소스에 대한 억세스를 제공한다.  새로운 롤을 정의하고 RoleBinding을 정의하는 권한을 포함하지만, resource quota에 대한 조정 기능은 가지지 않는다.

edit

None

네임스페이스내의 객체를 읽고 쓰는 기능은 가지지만, role이나 rolebinding을 쓰거나 수정하는 역할은 제외된다.

view

None

해당 네임스페이스내의 객체에 대한 읽기기능을 갔는다. role이나 rolebinding을 조회하는 권한은 가지고 있지 않다.


미리 정해진 롤에 대한 자세한 정보는  https://kubernetes.io/docs/reference/access-authn-authz/rbac/

를 참고하기 바란다.

권한 관리 예제

이해를 돕기 위해서 간단한 예제를 하나 테스트 해보자. 작성하는 예제는 Pod를 하나 생성해서 curl 명령으로 API를 호출하여, 해당 클러스터의 Pod 리스트를 출력하는 예제를 만들어보겠다.

Pod가 생성될때는 default 서비스 어카운트가 할당이 되는데, 이 서비스 어카운트는 클러스터의 정보를 호출할 수 있는 권한을 가지고 있지 않다. 쿠버네티스에 미리 정의된 ClusterRole중에 view 라는 롤은 클러스터의 대부분의 정보를 조회할 수 있는 권한을 가지고 있다.

이 롤을 sa-viewer 라는 서비스 어카운트를 생성한 후에, 이 서비스 어카운트에 ClusterRole view를 할당한후, 이 서비스 어카운트를 만들고자 하는 Pod에 적용하도록 하겠다.


apiVersion: v1

kind: ServiceAccount

metadata:

 name: sa-viewer

---

kind: ClusterRoleBinding

apiVersion: rbac.authorization.k8s.io/v1

metadata:

 name: default-view

subjects:

- kind: ServiceAccount

 name: sa-viewer

 namespace: default

roleRef:

 kind: ClusterRole

 name: view

 apiGroup: rbac.authorization.k8s.io


먼저 위와 같이 sa-viewer 라는 서비스 어카운트를 생성한후, ClusterRoleBiniding 을 이용하여, default-view라는 ClusterRolebinding 을 생성하고, sa-viewer 서비스 어카운트에, view 롤을 할당하였다.


다음 Pod를 생성하는데, 아래와 같이 앞에서 생성한 서비스 어카운트 sa-viewer를 할당한다.

apiVersion: v1

kind: Pod

metadata:

 name: pod-reader

spec:

 serviceAccountName: sa-viewer

 containers:

 - name: pod-reader

   image: gcr.io/terrycho-sandbox/pod-reader:v1

   ports:

   - containerPort: 8080


Pod 가 생성된 후에, kubectl exec 명령을 이용하여 해당 컨테이너에 로그인해보자

% kubectl exec -it pod-reader -- /bin/bash


로그인 후에 아래 명령어를 실행해보자


$ CA_CERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt

$ TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)

$ curl --cacert $CA_CERT -H "Authorization: Bearer $TOKEN" "https://35.200.91.132/api/v1/pods/"


CA_CERT는 API를 HTTPS로 호출하기 위해서 인증서를 저장한 파일의 위치를 지정하는 것이다. Pod의 경우에는 일반적으로 /var/run/secrets/kubernetes.io/serviceaccount/ca.crt  디렉토리에 인증서가 자동으로 설치 된다. 다음은 API TOKEN을 얻기 위해서 TOKEN 값을 가지고 온다. TOKEN은 cat /var/run/secrets/kubernetes.io/serviceaccount/token 에 디폴트로 저장이 된다.

다음 curl 명령으로 https:{API SERVER}/api/v1/pods 를 호출하면 클러스터의 Pod 리스트를 다음 그림과 같이 리턴한다.


\



사용자 관리에 있어서, 계정에 대한 정의와 권한 정의 그리고 권한의 부여는 중요한 기능이기 때문에, 개념을 잘 잡아놓도록하자.


쿠버네티스 #15

모니터링 3/3 구글 스택드라이버를 이용한 모니터링

조대협 (http://bcho.tistory.com)



구글 클라우드 쿠버네티스 스택드라이버 모니터링

쿠버네티스 모니터링 시스템을 구축하는 다른 방법으로는 클라우드 서비스를 사용하는 방법이 있다. 그중에서 구글 클라우드에서 제공하는 스택 드라이버 쿠버네티스 모니터링에 대해서 소개하고자한다.

https://cloud.google.com/monitoring/kubernetes-engine/


현재는 베타 상태로, 구글 클라우드 쿠버네티스 서비스 (GKE)에서만 지원이 되며, 쿠버네티스 버전 1.10.2 와 1.11.0 (또는 그 상위버전)에서만 지원이 되고, 모니터링 뿐 아니라, 쿠버네티스 서비스에 대한 로깅을 스택드라이버 로깅 서비스를 이용해서 함께 제공한다.


스택드라이버 쿠버네티스 모니터링을 설정하는 방법은 간단하다. 쿠버네티스 클러스터를 설정할때, 아래 그림과 같이 Additional features 항목에서 “Try the new Stackdriver beta monitoring and Logging experience” 항목을 체크하면 된다.



클러스터를 생성한 후에, 구글 클라우드 콘솔에서 Monitoring 메뉴를 선택한 후에



스택드라이버 메뉴에서 Resources 메뉴에서 아래 그림과 같이 Kubernetes 메뉴를 선택하면 쿠버네티스 모니터링 내용을 볼 수 있다.



모니터링 구조

스택드라이버 쿠버네티스 모니터링의 가장 큰 장점 중의 하나는 단순한 단일 뷰를 통해서 대부분의 리소스 모니터링 과 이벤트에 대한 모니터링이 가능하다는 것이다.

아래 그림이 스택드라이버 모니터링 화면인데, “2”라고 표시된 부분이 시간에 따른 이벤트이다. 장애등이 발생하였을 경우 아래 그림과 같이 붉은 색으로 표현되고, 3 부분을 보면, 여러가지 뷰 (계층 구조)로 각 자원들을 모니터링할 수 있다. 장애가 난 부분이 붉은 색으로 표시되는 것을 확인할 수 있다.



<출처 : https://cloud.google.com/monitoring/kubernetes-engine/observing >


Timeline에 Incident가 붉은 색으로 표시된 경우 상세 정보를 볼 수 있는데, Timeline에서 붉은 색으로 표시된 부분을 누르면 아래 그림과 같이 디테일 이벤트 카드가 나온다. 이 카드를 통해서 메모리,CPU 등 이벤트에 대한 상세 내용을 확인할 수 있다.



<출처 : https://cloud.google.com/monitoring/kubernetes-engine/observing >


반대로 정상적인 경우에는 아래 그림과 같이 이벤트 부분에 아무것도 나타나지 않고, 모든 자원이 녹색 동그라미로 표시되어 있는 것을 확인할 수 있다.


개념 구조

쿠버네티스 모니터링중에 어려운 점중의 하나는 어떤 계층 구조로 자원을 모니터링 하는가 인데, 이런점을 해결하기 위해서 구글 스택드라이버 쿠버네티스 모니터링은 3가지 계층 구조에 따른 모니터링을 지원한다. 모니터링 화면을 보면 아래와 같이 Infrastructure, Workloads, Services 와 같이 세가지 탭이 나오는 것을 볼 수 있다.



어떤 관점에서 클러스터링을 모니터링할것인가인데,

  • Infrastructure : 하드웨어 자원 즉, node를 기준으로 하는 뷰로,  Cluster > Node > Pod > Container 의 계층 구조로 모니터링을 제공한다.

  • Workloads : 워크로드, 즉 Deployment를 중심으로 하는 뷰로 Cluster > Namespace > Workload (Deployment) > Pod > Container 순서의 계층 구조로 모니터링을 제공한다.

  • Services : 애플리케이션 즉 Service 를 중심으로 하는 뷰로 Cluster > Namespace > Service > Pod > Container 계층 순서로 뷰를 제공한다.

Alert 에 대한 상세 정보

각 계층 뷰에서 리소스가 문제가 있을 경우에는 앞의 동그라미가 붉은색으로 표시가 되는데,  해당 버튼을 누르게 되면, Alert 에 대한 상세 정보 카드가 떠서, 아래 그림과 같이 이벤트에 대한 상세 정보를 확인할 수 있다.


<출처 : https://cloud.google.com/monitoring/kubernetes-engine/observing >

결론

지금까지 간단하게 쿠버네티스에 대한 모니터링과 로깅에 대해서 알아보았다. 프로메테우스나 그라파나와 같은 최신 기술을 써서 멋진 대쉬 보드를 만드는 것도 중요하지만 모니터링과 로깅은 시스템을 안정적으로 운영하고 장애전에 그 전조를 파악해서 대응하고, 장애 발생시에는 해결과 향후 예방을 위한 분석 및 개선 활동이 일어나야 한다. 이를 위해서 모니터링과 로깅은 어디까지나 도구일 뿐이고, 어떤 지표를 모니터링 할것인지 (SLI : Service Level Indicator), 지표의 어느값까지를 시스템 운영의 목표로 삼을 것인지 (SLO : Service Level Object)를 정하는 프렉틱스 관점이 더 중요하다.  이를 구글에서는 SRE (Site Reliability Engineering)이라고 하는데, 이에 대한 자세한 내용은 https://landing.google.com/sre/book.html 를 참고하기 바란다.

이런 프렉틱스를 구축하는데 목적을 두고, 모니터링을 위한 툴링등은 직접 구축하는 것보다는 클라우드에서 제공하는 스택 드라이버와 같은 솔루션이나 데이타독(Datadog)와 같은 전문화된 모니터링 툴로 구축을 해서 시간을 줄이고, 프렉틱스 자체에 시간과 인력을 더 투자하는 것을 권장한다.




쿠버네티스 #14

모니터링 2/3 Prometheus를 이용한 모니터링


조대협 (http://bcho.tistory.com)

프로메테우스

그동안 주요 모니터링 솔루션으로 사용되던 힙스터는 1.13 버전 이후로 deprecated 될 예정이고, 그 이후를 맏을 모니터링 솔루션으로 가장 많이 언급되는 모니터링 솔루션은 프로메테우스 (Prometheus)이다.


프로메테우스는 SoundCloud (http://soundcloud.com/)에서 개발된 모니터링 툴로, 2016년에 CNCF  (Cloud Native Computing Foundation)에 오픈소스 프로젝트로 기부되었다. 지표 수집을 통한 모니터링을 주요 기능으로 하고 있다.


쿠버네티스 모니터링뿐만 아니라 애플리케이션이나 서버, OS 등 다양한 대상으로 부터 지표를 수집하여 모니터링할 수 있는 범용 솔루션으로, 아래와 같은 구조를 가지고 있다.



<그림. 프로메테우스 모니터링 아키텍처>

데이타 수집 부분

기본적으로, 프로메테우스는 데이타 수집을 PULLING 모델을 사용한다. 모니터링 대상이 되는 자원이 지표 정보를 프로메테우스로 보내는 것이 아니라, 프로메테우스가 주기적으로 모니터링 대상에서 지표를 읽어 오는 모델을 사용한다.


모니터링 대상이 프로메테우스의 데이타 포맷을 지원할 경우 바로 읽어올 수 있고, 만약에 지원하지 않는다면 별도의 에이전트를 설치해서 지표를 읽어올 수 있는데, 이를 exporter라고 한다. exporter는 mysql,nginx,redis와 같은 패키지는 미리 개발된 export가 있어서 다양한 서비스의 지표까지 쉽게 읽어올 수 있다.

이런 패키지 애플리케이션이 아니라, java 나 node.js와 같은 사용자 애플리케이션의 경우에는 Exporter를 사용하는 방법 말고도, 프로메테우스 클라이언트 라이브러리를 사용하게 되면, 바로 지표를 프로메테우스 서버로 보낼 수 있다.

마지막으로, Push gateway를 사용하는 방법이 있는데, 배치나 스케쥴 작업 같은 서비스의 경우에는 항상 서비스가 떠 있는 것이 아니라, 필요한 경우에만 떠 있다가 작업이 끝나면 사라지는 경우가 있다. 그래서, 이런 서비스를 Pulling으로 지표를 얻어오기가 어려울 수 있는데, 이를 보완하기 위해서, 이런 서비스들이 Push 방식으로 Push gateway에 지표를 쏴주면, Push gateway가 지표를 보관하고 있다가 프로메테우스 서버가 Pulling 을 하면, 저장된 지표 정보를 리턴하도록 한다.

서비스 디스커버리

그러면 프로메테우스는 모니터링 대상을 어떻게 알 수 있을까? 당연히 모니터링 대상 목록을 유지하고 있고, 대상에 대한 IP나 기타 접속 정보를 설정 파일에 주면, 그 정보를 기반으로 프로메테우스 서버가 모니터링 정보를 읽어온다.

그러나 오토스케일링을 많이 사용하는 클라우드 환경이나 쿠버네티스와 같은 컨테이너 환경에서는 모니터링 대상의 IP가 동적으로 변경되는 경우가 많기 때문에 이를 일일이 설정파일에 넣는데 한계가 있다. 이러한 문제를 해결 하기 위해서 프로메테우스는 서비스 디스커버리 를 사용하는데, 모니터링 대상이 등록되어 있는 저장소에서 목록을 받아서 그 대상을 모니터링 하는 형태이다.


프로메테우스는 DNS나 Consul, etcd와 같은 다양한 서비스 디스커버리 서비스와 연동을 통해서 자동으로 모니터링 대상의 목록을 가지고 올 수 있다.

저장 및 시각화

이렇게 수집된 지표 정보들은 프로메테우스 내부의 시계열 데이타베이스에 저장이 되고, 프로메테우스 웹 콘솔을 이용하여 시각화 되거나 또는 API를 외부에 제공해서 Grafana와 같은 시각화 툴을 통해서 지표를 시작화 해서 볼 수 있다.

알림 서비스

부가 기능중의 하나로, alerting 컴포넌트는, 지표에 대한 규칙을 걸어놓고 그 규칙을 위반할 경우에는 알림을 보낼 수 있는 기능을 가지고 있다. 알림을 보내는 대상은 이메일이나 pagerduty와 같은 notification 서비스 등과 연동이 가능하다.

쿠버네티스 연동 아키텍처

그러면, 쿠버네티스와 프로메테우스는 어떻게 연동이 될까? 여기서 오해하지 말아야 하는 점은 Heapster,cAdvisor 스택과 같이 딱 정해진 아키텍쳐는 없다는 것이다. 프로메테우스는 범용 모니터링 솔루션으로 프로메테우스 서버가 지표정보를 읽어올 수 만 있다면 거의 모든 정보를 읽어올 수 있는 구조이기 때문에, 쿠버네티스 연동에 있어서도 자유도가 매우 높다


단 레퍼런스 할 수 있는 구성은 있는데, 다음과 같은 구조를 갖는다.


먼저 프로메테우스 서버가 모니터링할 리소스를 찾기 위해서 서비스 디스커버리 (Service discovery) 메카니즘이 필요한데, 이를 위해서 쿠버네티스 API를 호출해서, 자원들의 목록 (Pod,Node, Service,Ingress,Endpoint 등)의 목록을 라벨 셀렉터(label selector)를 이용하여 수집한다.

다음 수집된 모니터링 대상에 대해서 모니터링을 수행하는데, 쿠버네티스는 apiServer에서 /metric 이라는 URL을 통해서 기본적인 지표 정보를 리턴하기 때문에, 쿠버네티스 자원들에 대한 모니터링은 이 API를 통해서 수집하게 된다.


아랫단에 하드웨어 즉 node에 대한 정보는 API를 통해서 수집하기가 어렵기 때문에, node에 node exporter를 설치해서 하드웨어와 OS에 대한 정보를 수집한다. 컨테이너에 대한 정보는 node별로 배포되어 있는 cAdvisor가 이를 수집하여 프로메테우스에 제공한다.


컨테이너내에서 기동되는 애플리케이션에 대한 정보는 필요한 경우, 클라이언트 SDK나, 솔루션에 맞는 exporter를 이용해서 수집한다.

쿠버네티스 연동하기

그러면 실제로 프로메테우스를 설치해서 쿠버네티스 클러스터를 모니터링 해보자. 앞의 아키텍쳐에서 봤지만, alert server, exporter, prometheus server 등 설치해야 하는 서버들이 많아서, 일일이 설치하는 것이 쉽지 않다. 여러가지 설치 방법이 있지만 여기서는 쿠버네티스의 패키지 매니저인 Helm 을 이용해서 프로메테우스를 설치하도록 한다. Helm 은  Linux의 RPM이나, Node.js의 npm같이 소프트웨어 스택을 명령으로 손쉽게 설치할 수 있도록 해주는 패키지 매니져의 개념으로 쿠버네티스 버전의 npm 툴이라고 이해하면 된다.


참고로 여기서 설치는 로컬 PC의 minikube 환경을 이용해서 설치하였다. 클라우드 환경에서 제공되는 쿠버네티스 클러스터의 경우에는 다소 차이가 있을 수 있으니, 각 벤더에서 제공되는 가이드를 참고하기 바란다. 아울러 아래 설치 내용은 운영 환경에서 적용하기는 어렵고, 운영환경 적용을 위해서는 적절한 디스크 타입과 Pod의 사이즈등을 다시 클러스터 환경에 맞도록 설정해야하고 어디까지나, 테스트 용임을 인지하기 바란다.

Helm 인스톨

Helm은 클라이언트와 서버 두개의 모듈로 나뉘어 진다.

인스톨은 어렵지 않은데, 클라이언트 OS에 따라 약간씩 차이가 있다. 자세한 인스톨 방법은 https://docs.helm.sh/using_helm/ 문서를 참고하면 된다.

클라이언트 인스톨

맥에서 클라이언트 인스톨은 brew를 이용하면 쉽게할 수 있다.

%brew install kubernetes-helm

명령을 이용하면 Helm 클라이언트가 로컬 PC에 설치된다.

서버 인스톨

Helm 서버를 Tiller라고 하는데, Tiler 서버의 인스톨은 어렵지 않으나, 클라우드 벤더나 설치 환경에 따라서 약간씩의 차이가 있다.


Minikube  환경에서 인스톨

Minikube 환경에서 인스톨은 Helm 클라이언트를 인스톨 한 후에, 아래와 같이

%helm init

명령어를 실행하면 쿠버네티스 클러스터에 Tiller 서버가 자동으로 설치된다.


구글 클라우드 쿠버네티스 엔진 (GKE) 환경에서 인스톨

GKE 환경은 약간 설치 방법이 다른데, 보안적인 이슈로 인해서 계정에 대한 권한 컨트롤을 상대적으로 까다롭게 하기 때문이다.

(참고 : https://cloud.google.com/solutions/continuous-integration-helm-concourse )


아래 명령을 이용하면 kube-system 네임 스페이스에 tiller라는 이름으로 서비스 어카운트를 생성할 수 있다.

% kubectl create clusterrolebinding user-admin-binding --clusterrole=cluster-admin --user=$(gcloud config get-value account)

% kubectl create serviceaccount tiller --namespace kube-system

% kubectl create clusterrolebinding tiller-admin-binding --clusterrole=cluster-admin --serviceaccount=kube-system:tiller


다음 Tiller를 생성할때, --service-account=tiller 옵션을 줘서 tiller 가 실행될때, 해당 서비스 어카운트의 권한을 가지고 실행되도록 한다.


헬름 서버 (Tieller) 인스톨

./helm init --service-account=tiller
./helm update


이렇게 설치 하지 않으면 Tiller 자체는 설치가 될 수 있지만, Tiller에 의해서 인스톨 되는 패키지들이 권한 오류로 인해서 제대로 설치되지 않을 수 있다

Helm Chart를 이용한 Prometheus 설치

Helm이 준비되었으면 프로메테우스 를 설치해보자


% git clone https://github.com/kubernetes/charts

명령을 이용하여 Helm chart를 다운 받는다. Helm chart는 npm 파일과 같이 인스톨 스크립트를 모아놓은 것으로 생각하면 된다. 프로메테우스외에도 다양한 설치 스크립트가 있다.


$ cd charts/stable/prometheus

를 이용해서 프로메테우스 디렉토리로 들어간 후에, 아래 명령을 이용하면 prometheus 네임스페이스에 프로메테우스가 설치된다.


$ helm install -f values.yaml stable/prometheus --name prometheus --namespace prometheus


설치가 끝났으면 이제 프로메테우스가 제대로 작동해서 지표를 수집하고 있는지 확인하자. 프로메테우스 서버는 디폴트로 9090 포트를 통해서 웹 인터페이스를 제공한다. 프로메테우스 서버를 외부 서비스로 expose 하지 않았기 때문에 포트 포워딩을 이용해서 프로메테우스 서버의 9090 포트를 포워딩 해보자


%kubectl get pod -n prometheus

명령을 이용해서 prometheus 네임스페이스에 있는 pod 목록을 다음과 같이 가지고 온다.



prometheus의 pod 명이 “prometheus-server-5695758946-gdxjx” 인것을 알았으면,localhost:9090을 이 pod의 9090포트로 포워딩하도록 설정한다.

%kubectl port-forward -n prometheus prometheus-server-5695758946-gdxjx 9090


포트 포워딩이 설정되었으면 localhost:9090으로 접속하여 프로메테우스의 웹 콘솔을 접속해보자

처음에는 아무것도 나오지 않을텐데, metric을 PQL (프로메테우스 쿼리)를 이용해서 선택하면 아래와 같이 해당 지표에 대한 값이 나오는것을 볼 수 있다. 아래는 node의 disk_io 정보를 살펴보는 쿼리이다.



이 메뉴에서 지표를 모니터링 하거나 또는 모니터링된 지표를 Graph 탭을 눌러서 그래프로 시각화 할 수 있다. 메뉴를 조금더 둘러보면 상단의 Status 메뉴에서 Service Discovery 메뉴를 눌러보면 다음과 같은 결과를 얻을 수 있다.


모니터링해야 하는 자원들의 목록으로 node, node-cadvisor, pods, services 등에 대한 정보를 모니터링할 수 있는 것을 확인할 수 있다.


Target 메뉴를 클릭하면 다음과 같은 정보가 나오는데,


어디로 부터 지표들을 수집해오는지 URL등을 확인할 수 있다. apiserver의 URL, node metric 정보 수집 URL node cAdvisor 수집 URL등을 확인할 수 있다.

Helm Chart를 이용한 Grafana 설치

프로메테우스를 설치했으면 이를 시각화 하기 위해서 Grafana를 설치해서 연동해보도록 하자.

Helm chart 디렉토리에서 stable/grafana 디렉토리에 values.yaml 파일이 있는데, 이 부분에서 adminPassword 부분을 찾아서 admin 사용자의 비밀 번호를 세팅하도록 하자.


adminUser: admin

adminPassword: mypassword


다음 Helm chart를 이용해서 Grafana를 설치한다.

stable/grafana 디렉토리에서 앞에서 수정한 values.yaml 파일을 이용한다.

%helm install -f values.yaml stable/grafana --name grafana --namespace grafana


설치가 종료되었으면 Grafana 콘솔에 접속해보자.

%kubectl get pod -n grafana 명령을 이용해서 grafana 서버의 pod 명을 알아낸다.


Grafana 서버는 외부 서비스로 Expose 되지 않았기 때문에, 포트 포워딩을 이용해서 해당 서버에 접속하도록 한다. Grafana는 3000번 포트로 웹 접속을 허용한다.


% kubectl port-forward -n grafana grafana-679cdd7676-zhwnf 3000

명령을 이용하면 localhost:3000을 Grafana 웹 서버로 포워딩 해준다.

localhost:3000에 접속해보면 다음과 같은 로그인 창이 나온다.


로그인창에서, 사용자명을 admin으로 입력하고, 비밀번호는 앞의 설정에서 입력한 비밀번호를 설정한다.

다음으로 프로메테우스 서버를 데이타 소스로 설정해야 하는데, grafana 메뉴에서 Configuration > Data source 메뉴를 선택한다.



Data source를 추가하기 위해서는 프로메테우스 서버의 URL 을 알아야 하는데, 프로메테우스 서버는 내부 IP를 가지고 있는 서비스로 Expose 되어 있다. 서비스명을 알기 위해서 다음 명령어를 실행한다.

%kubectl get svc -n prometheus

다음과 같이 서비스명이 prometheus-server이고 cluster-IP가 10.102.173.250 인것을 확인할 수 있다.




HTTP URL은 http://prometheus-server.prometheus.svc.cluster.local 게 된다.

그러면 이 정보를 Grafana datasource 쪽에 추가한다.



데이타소스 명은 Kuberentes로 지정하고, 타입은 Prometheus로 지정한다. 그리고 HTTP URL은 위의 http://prometheus-server.prometheus.svc.cluster.local 를 사용하고 Access 타입은 Server를 선택한다.


이 과정이 끝나면, 프로메테우스를 Grafana의 데이타 소스로 사용할 수 있다.

이 데이타 소스를 이용해서 대쉬 보드를 구성해야 하는데, 수동으로 일일이 구성할 수 도 있지만 Grafana 커뮤니티에는 이미 미리 구성되어 있는 대쉬보드 템플릿이 많다. 이 템플릿을 그대로 import 해서 사용해보도록 하겠다.

Grafana 메뉴에서 아래와 같이 Create > Import 메뉴를 선택한다.


다음 대쉬보드 설정 JSON을 넣을 수 있는데, 또는 Grafana.com에 등록된 대쉬보드 템플릿 번호를 넣을 수 도 있다. 여기서는 쿠버네티스 클러스터 모니터링 템플릿을 사용하도록 하겠다. 이 템플릿의 ID는 1621번이기 때문에 아래와 같이 템플릿 ID를 입력한다.

이 템플릿 이외에도, 노드 모니터링을 위한 템플릿등 여러 종류의 대쉬 보드 템플릿이 있기 때문에 용도에 맞게 선택해서 사용하면 된다.


템플릿 ID를 선택하면 다음 화면에서 데이타 소스를 선택해줘야 하는데, 아래 그림과 같이 Prometheus 부분을 앞에서 만든 데이타 소스 이름인 Kubernetes를 선택한다.


설정이 끝난후에 대쉬보드를 확인하면 아래와 같이 쿠버네티스에 대한 전반적인 모니터링 정보가 나오는 것을 확인할 수 있다.





쿠버네티스 #13

모니터링 1/2


조대협 (http://bcho.tistory.com)


시스템을 운영하는데 있어서 운영 관점에 있어서 가장 중요한 기능중의 하나는 시스템에 대한 모니터링이다. 시스템 자원의 사용량이나 에러등에 대한 모니터링을 통해서, 시스템을 안정적으로 운영하고 문제 발생시 원인 파악과 대응을 할 수 있다.

이번 글에서는 쿠버네티스 모니터링 시스템에 대한 개념과, 아키텍쳐 그리고 구축 방법에 대해서 소개하고자 한다.

쿠버네티스 모니터링 컨셉

쿠버네티스에 대한 모니터링을 보면 많은 툴과 지표들이 있어서 혼돈하기 쉬운데, 먼저 모니터링 컨셉에 대한 이해를 할 필요가 있다.

쿠버네티스 기반의 시스템을 모니터링하기 위해서는 크게 아래와 같이 4가지 계층을 모니터링해야 한다.



1. 호스트 (노드)

먼저 쿠버네티스 컨테이너를 실행하는 하드웨어 호스트 즉 노드에 대한 지표 모니터링이 필요하다. 노드의 CPU,메모리, 디스크, 네트워크 사용량과, 노드 OS와 커널에 대한 모니터링이 이에 해당한다.

2. 컨테이너

다음은 노드에서 기동되는 각각의 컨테이너에 대한 정보이다. 컨테이너의 CPU,메모리, 디스크, 네트워크 사용량등을 모니터링 한다.

3. 애플리케이션

컨테이너안에서 구동되는 개별 애플리케이션의 지표를 모니터링 한다. 예를 들어, 컨테이너에서 기동되는 node.js 기반의 애플리케이션의 응답시간, HTTP 에러 빈도등을 모니터링한다.

4. 쿠버네티스

마지막으로, 컨테이너를 컨트롤 하는 쿠버네티스 자체에 대한 모니터링을한다. 쿠버네티스의 자원인 서비스나 POD, 계정 정보등이 이에 해당한다.

쿠버네티스 기반의 시스템 모니터링에 대해서 혼돈이 오는 부분중의 하나가 모니터링이라는 개념이 포괄적이기 때문이다. 우리가 여기서 다루는 모니터링은 자원에 대한 지표 대한 모니터링이다. 포괄적인 의미의 모니터링은 로그와, 에러 모니터링등 다양한 내용을 포괄한다.  

쿠버네티스 로깅

지표 모니터링과 함께 중요한 모니터링 기능중 하나는 로그 수집 및 로그 모니터링이다.

로그 수집 및 로그 모니터링 방법은 여러가지 방법이 있지만, 오픈소스 로그 수집 및 모니터링 조합인 EFK (Elastic search + FluentD + Kibina) 스택을 이용하는 경우가 대표적이다.

Fluentd 에이전트를 이용하여, 각종 로그를 수집하여, Elastic search에 저장하고, 저장된 지표를 Kibana 대쉬 보들르 이용하여 시작화 해서 나타내는 방법이 있다.

이에 대한 자세한 설명을 생략한다.

쿠버네티스 모니터링 시스템 구축

그러면 이러한 모니터링 시스템을 어떻게 구축할 것인가?

쿠버네티스 모니터링은 버전업 과정에서 많은 변화를 겪고 있다. 기존 모니터링 시스템의 아키텍쳐는 cAdvisor,Heapster를 이용하는 구조였으나, 이 아키텍쳐는 곧 deprecated 될 예정이고, Prometheus등 다양한 모니터링 아키텍쳐가 후보로 고려 되고 있다.

아래 그래프를 보면 재미있는 통계 결과가 있는데, cAdvisor,Heapster,Promethus 를 이용하는 방법도 있지만, 클라우드의 경우에는 클라우드 벤더에서 제공하는 쿠버네티스 모니터링 솔루션을 그대로 사용하거나 (18%) 또는 데이타독이나 뉴렐릭 (Datadog, newRelic)과 같이 전문화된 모니터링 클라우드을 사용하는 비율 (26%) 도 꽤 높다.



<그림. 쿠버네티스 모니터링 솔루션 분포 >

출처 :  https://thenewstack.io/5-tools-monitoring-kubernetes-scale-production/


개인적인 의견으로는 직접 모니터링 솔루션을 구축해서 사용하는 것보다는 비용은 약간 들지만 클라우드 벤더에서 제공되는 모니터링 도구나 또는 데이타독과 같은 전문 모니터링 솔루션을 이용하는 것을 추천한다.


직접 모니터링 솔루션을 구축할 경우 구축과 운영에 드는 노력도 꽤 크고, 또한 어떠한 지표를 모니터링해야할지 등에 대한 추가적인 노하우가 필요하다. 또한 cAdvisor,Heapster,Promethues 조합은 호스트와 컨테이너 그리고 쿠버네티스에 대한 모니터링은 제공하지만 애플리케이션 지표에 대한 모니터링과 로깅 기능은 제공하지 않기 때문에 별도의 구축이 필요하다. 이런 노력을 들이는 것 보다는 모든 기능이 한번에 제공되고 운영을 대행해주는 데이타독이나 클라우드에서 제공해주는 모니터링 솔루션을 사용하는 것을 추천한다.

Heapster 기반 모니터링 아키텍처

이러한 모니터링 요건을 지원하기 위해서, 쿠버네티스는 자체적인 모니터링 컴포넌트를 가지고 있는데, 그 구조는 다음과 같다.



<그림. 쿠버네티스 모니터링 시스템 아키텍쳐>

출처 Source : https://www.datadoghq.com/blog/how-to-collect-and-graph-kubernetes-metrics/


cAdvisor

cAdvisor는 모니터링 에이전트로, 각 노드마다 설치되서 노드에 대한 정보와 컨테이너 (Pod)에 대한 지표를 수집하여, Kubelet으로 전달한다.

Heapster

cAdvisor에 의해 수집된 지표는 Heapster 라는 중앙 집중화된 지표 수집 시스템에 모이게 되고, Heapster는 수집된 지표를 스토리지 백앤드에 저장한다.

Storage backend

Heapster가 지표를 저장하는 데이타베이스를 스토리지 백앤드라고 하는데, Heapster는 확장성을 위해서 다양한 스토리지 백앤드를 플러그인 구조를 선택하여 연결할 수 있다.

현재 제공되는 대표적인 스토리지 백앤드는 구글 클라우드의 모니터링 시스템인 스택드라이버 (stackdriver), 오픈 소스 시계열 데이타베이스인 인플럭스 디비 (InfluxDB) 등을 지원한다.

그래프 대쉬 보드

이렇게 저장된 모니터링 지표는 그래프와 같은 형태로 시각화 될필요가 있는데, 스토리지 백앤드를 지원하는 다양한 시각화 도구를 사용할 수 있다. 구글의 모니터링 시스템인 스택드라이버의 경우에는 자체적인 대쉬보드 및 그래프 인터페이스가 있고, 인플럭스 디비나 프로메테우스의 경우에는 오픈소스 시각화 도구인 그라파나(Grafana)를 사용할 수 있다.


<그림. 그라파나와 프로메테우스를 연결하여, 지표 모니터링을 시각화 한 예제>


그러나 이 아키텍쳐는 deprecation 계획이 시작되서 1.13 버전 부터는 완전히 제거될 예정이다.

https://github.com/kubernetes/heapster/blob/master/docs/deprecation.md


쿠버네티스 대시보드

다른 방법으로는 쿠버네티스를 모니터링 하고 관리할 수 있는 쉬운 방법이 하나 있는데, 쿠버네티스 대시보드를 사용하는 방법이다. 쿠버네티스는 기본적으로 kubectl이라는 커맨드 라인 인터페이스 (이하 CLI : Command Line Interface)를 사용하지만, 추가적으로 웹 기반의 관리 콘솔을 제공한다. 이를 쿠버네티스 대시보드라고 한다. (https://github.com/kubernetes/dashboard)

대시 보드 설치

쿠버네티스 대시 보드 설치 방법은 간단하다. 아래와 같이 대시보드 설정 yaml 파일을 이용하면 간단하게 대시 보드를 쿠버네티스 클러스터에 설치할 수 있다.


% kubectl create -f https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml


일반적인 경우에는 위의 스크립트로 설치가 가능하지만, 구글 클라우드 쿠버네티스 엔진의의 경우에는 설치 중에 권한 관련 에러가 나올 수 있는데, 구글 클라우드 쿠버네티스 엔진의 경우에는 보안을 이유로 일반적인 쿠버네티스보다 권한 설정 레벨이 높게 설정되어 있기 때문이다. 구글 클라우드 쿠버네티스 엔진에서 대시보드를 설치하고자할때에는 위의 스크립트를 실행하기 전에 먼저 아래 명령어를 이용해서, 현재 사용자 계정에 대해서 cluster-admin 롤을 부여해줘야 한다.  


%kubectl create clusterrolebinding cluster-admin-binding \
--clusterrole cluster-admin --user $(gcloud config get-value account)

대시 보드 접속

대시보드 설치가 끝났으면, 대시보드를 접속해보자

대시보드는 외부 서비스로 제공되지 않고, 내부 IP로만 접속이 가능한데, 클러스터 외부에서 접근하려면 kubectl proxy를 이용하면, 간단하게 접근이 가능하다.

kubectl proxy는 로컬 머신 (예를 들어 노트북)과 쿠버네티스 클러스터간의 통신을 프록싱해줘서, 로컬 머신에서 쿠버네티스 클러스터내의 HTTP 서비스를 접근할 수 있도록 해준다.

사용 방법은 로컬 머신에서 간단하게

%kubectl proxy

명령을 실행해주면 localhost:8001 포트를 통해서 쿠버네티스 클러스터로 트래픽을 프록시 해준다.

위와 같이 proxy를 실행한후에,  아래 URL로 접근을 하면, 대시보드 콘솔에 접근할 수 있다.

http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/


URL에 접근하면 아래와 같이 로그인 창이 나타난다.



사용자 계정 및 토큰등에 대해서는 보안 부분에서 별도로 다루기로 하겠다.

대쉬보드를 사용하기 위해서는 사용자 인증이 필요한데, 간단하게 인증을 위한 토큰을 사용하는 방법을 이용하도록 하겠다.

토큰은 쿠버네티스 API 인증 메커니즘중의 하나로, 여기서는 admin-user라는 계정을 하나 만든후에, 그 계정에, 클러스터 관리자롤을 부여한 후에, 그 사용자의 토큰을 사용하는 방법을 사용하겠다.


먼저 아래 스크립트를 이용해서 admin-user 라는 사용자를 생성한다.

admin-user.yaml 파일

apiVersion: v1

kind: ServiceAccount

metadata:

 name: admin-user

 namespace: kube-system


다음 아래 스크립트를 이용해서 cluster-admin 롤을 앞에서 생성한 admin-user에 부여한다.

admin-rolebinding.yaml 파일

apiVersion: rbac.authorization.k8s.io/v1beta1

kind: ClusterRoleBinding

metadata:

 name: admin-user

roleRef:

 apiGroup: rbac.authorization.k8s.io

 kind: ClusterRole

 name: cluster-admin

subjects:

- kind: ServiceAccount

 name: admin-user

 namespace: kube-system


다음 아래 명령어를 이용하면 admin-user의 토큰 값을 알 수 있다.

% kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-user | awk '{print $1}')


명령을 실행하면 아래와 같이 토큰이 출력된다.


이 토큰 값을 앞의 로그인 창에 입력하면, 대시보드에 로그인할 수 있다.

대시 보드에 로그인하면 아래와 같이 노드나, Pod, 서비스등 쿠버네티스의 자원의 대부분의 정보에 대한 모니터링이 가능하다.




또한 kubectl CLI 명령을 사용하지 않고도 손쉽게 Deployment 등 각종 자원을 생성할 수 있다.


로그 부분에 들어가면 아래와 같이 로그 정보를 볼 수 있다



재미있는 기능중 하나는 아래 그림과 같이 특정 Pod의 컨테이너를 선택하면, 웹콘솔상에서 해당 컨테이너로 SSH 로그인이 가능하다.



여기서 다룬 쿠버네티스 대시보드 설정 및 로그인 부분은 프록시 사용, 로그인을 토큰을 사용하는 등, 운영환경에는 적절하지 않은 방법이다. 개발환경이나 테스트 용도로만 사용하도록 하고, 운영 환경에서는 사용자 계정 시스템 생성과 적절한 권한 배정을 한 후에, 적절한 보안 인증 시스템을 마련한 후에 적용하도록 하자.