Kubernetes Raspberry Pi 소형 IDC 구축

개요

가상머신이 아닌 실제 하드웨어 규모로 컨테이너 기술을 실험하고 싶었다. 가장 큰 목적으로는 이동하며 kubernetes가 어떻게 작동하는지 시뮬레이션 가능하도록 하는 것이었다.

목적에 적합한 컴팩트한 사이즈와 전력 소비가 적고 저렴한 rapberry pi를 사용하기로 결정하였고 소형 IDC 구상을 시작하였다. 국내에는 rpi를 이용하여 cluster 구성을한 사례를 찾아 볼 수 없었다.

해외 rpi cluster 구축 사이트를 참조하여 구상을 하던 중 아크릴 판을 이용하여 케이스를 제작한 문서를 참고하여 구축하게 되었다.

Read more...

Kubernetes Internal DNS

kubernetes dns is for service objects. DNS is served as a pod in the cluster.

Set up kubelet to use DNS

Modify kubelet definition file(/etc/kubernetes/kubelet) on all minions. Add the following options.

Read more...

Kubernetes TLS Setup Howto

Secure communication on Kubernetes cluster.

Kubernetes apiserver supports both insecure HTTP and secure HTTPS/TLS protocol.

The insecure HTTP with port 8080 is the default setup but as the name indicates, it is not secure. So we need to set up secure HTTPS communication on KUBIC cluster. Let’s see how to do it.

Read more...

Kubernetes TLS Setup Howto (ko)

Kubernetes 클러스터의 보안 통신

Kubernetes api서버는 비보안의 HTTP와 보안 HTTPS/TLS 프로토콜 모두를 지원합니다.

8080 포트를 사용하는 비보안 HTTP는 기본으로 설정되지만, 이름에서 알수 있듯 안전하지 않습니다. 따라서 KUBIC 클러스터에서 안전한 HTTPS 통신을 설정해야 합니다. 그 방법을 기술 합니다.

Read more...

kube-proxy load balance test (en)

1. What is kube-proxy?

Kubernetes is an orchestration tool for containers. kube-proxy is a component of kubernetes running on kubernetes minion. Its role is the network proxy and load balancer for each container. It uses iptables statistic extension module with random mode and probability settings.

Read more...

kube-proxy load balance test (ko)

1. kube-proxy란?

Kubernetes는 컨테이너를 위한 통합 도구입니다. kube-proxy의 특징은 다음과 같습니다.

  • kube-proxy는 kubernetes minion에 실행되는 kubernetes의 구성 요소입니다.
  • kube-proxy는 각각의 컨테이너에서 네트워크 프록시 및 load balancer 역할을 합니다.
  • kube-proxy는 랜덤 모드 및 확률 설정을 포함한 iptables 통계 확장 모듈을 사용합니다.

Read more...

django로 csrf token의 post request 전달하는 방법

운영중인 서버의 was(fcgi)가 가끔 비정상적으로 동작한다.

crontab에 등록하여 일주일에 한번씩 was(fcgi를 restart하고 있지만, 그래도 문제가 발생한다. restart 주기를 더 짧게 설정하는 방법보다 문제가 발생했을때 restart하는것이 효율적일것이다. curl을 이용하여 실질적으로 login하는 과정을 거쳐 main page(/bom/register/)가 response status 200이 아니면 restart 하게끔 script 작성하고자 한다.

curl을 이용한 login

현재 서버의 login page는 http://xxx.xxx.xxx/login/ 이다. 해당 page를 body(post)값으로 username과 password 변수를 같이 넘겨준다면 login이 될것이다.

# curl http://xxx.xxx.xxx/login/ -d "username={ID}&password={PW}"

그런데 403 FORBIDDEN이 뜬다. 원인은 CRSF verification failed다.

<h1>Forbidden <span>(403)</span></h1>
  <p>CSRF verification failed. Request aborted.</p>

CSRF

사이트 간 요청 위조(또는 크로스 사이트 요청 위조, 영어: Cross-site request forgery, CSRF, XSRF)는 웹사이트 취약점 공격의 하나로, 사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위(수정, 삭제, 등록 등)를 특정 웹사이트에 요청하게 하는 공격을 말한다.

예를 들자면 아래와 같은 공격이다.

  1. 사용자가 xxx.xxx.xxx에 login을 한다. sessionid등의 쿠키값을 정상적으로 발급받아 login된다.
  2. 공격자가 mail이나 게시판등을 이용해 악의적인 http request의 주소를 사용자쪽으로 전달한다. 예를 든다면 http://xxx.xxx.xxx/changepassword?password=abc와 같은 request를 서버로 전송하게끔.
  3. 사용자가 해당 주소를 실행하여 원하지 않는 request를 전송하게 된다.
  4. 서버입장에선 로그인을 거친 정상적인 client가 request를 수행한것이니 해당 프로세스를 수행한다.

위 문제를 해결 하기 위해선 changpassword 페이지의 form전달값에 특정한 값을 추가하면 된다.

예를 들어 사용자로 부터 captcha를 값을 입력받게하여, 정상적인 페이지에서의 요청인지 확인하면 되는것이다. django에서는 간단히 crsf token을 발행하여 처리하게 한다.

CSRF TOKEN in DJANGO

form를 가진 django template에 아래와 같이 crsf_input을 설정한다.

<form action="" method="post">{{ csrf_input }}
<input name="username" id="username" type="text" />
<input name="password" id="password" type="password" />

이렇게 되면 아래와 같은 client에서는 아래와 같은 response를 받게 된다.

<form action="" method="post"><input type='hidden' name='csrfmiddlewaretoken' value='d7f6f683188d35958b0f453f6849a8d7' />

csrfmiddlewaretoken라는 이름의 hidden변수에 random 생성된 token값이 날라온다. http header에도 동일한 token을 cookie로 저장하게끔 되어 있다.

Set-Cookie: csrftoken=d7f6f683188d35958b0f453f6849a8d7; expires=Mon, 10-Jul-2017 08:32:17 GMT; Max-Age=31449600; Path=/

따라서 request시 cookie와 body에 위와 동일한 token값을 같이 전송해야 한다. django에서 해당 token값이 없거나 잘못되었을 경우 위처럼 403을 띄우게 되는것이다.

CURL을 이용하여 CRSF token 전송

CSRF token을 받아와야하니 한번의 login page request로는 불가능하다. 아래와 같은 과정이 있어야 할것이다.

  1. http://xxx.xxx.xxx/login/ 요청후에 csrf token값 저장
  2. 전달받은 csrf token값을 username, password와 같이 전달. cookie도 마찬가지.

curl을 이용하여 csrf token값을 저장해보자.

# curl -c cookie.txt http://xxx.xxx.xxx/login/

-c 옵션으로 set-cookie를 처리할 수 있다. 즉 cookie.txt가 생성되고 csrf token값이 저장된다. 파일을 열어보면 아래와 같다.

# Netscape HTTP Cookie File
# http://curl.haxx.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
xxx.xxx.xxx FALSE   /   FALSE   1499671503  csrftoken   d7f6f683188d35958b0f453f6849a8d7

이제 해당 cookie값과 post값으로 csrf token값을 전달하면 된다. -b 옵션으로 저장된 cookie을 서버로 전달할수 있다.

# curl -b cookie.txt http://xxx.xxx.xxx/login/ -d "username={ID}&password={PW}&csrfmiddlewaretoken=d7f6f683188d35958b0f453f6849a8d7"

이제 정상적으로 login이 됨을 확인할수 있다. 그런데 /bom/register/ 페이지가 정상적으로 열리지 않는다. login후에 sessionid을 cookie로 처리해야하는데, 그 과정이 없으니 login이 되지 않은 것과 동일한 것이다. 위 login request시 sessionid도 response header로 아래와 넘어 온다.

Set-Cookie: sessionid=a37046e1944553ee8dc2af0bc5c483fc; Path=/

그러니 위의 sessionid도 같이 cookie로 저장해야한다. 위와 동일하게 -c 옵션을 줘서 sessionid도 저장하게한다.

# curl -b cookie.txt http://xxx.xxx.xxx/login/ -d "username={ID}&password={PW}&csrfmiddlewaretoken=d7f6f683188d35958b0f453f6849a8d7" -c cookie.txt

cookie.txt파일을 열어보면 csrftoken값과 sessionid가 같이 저장 됨을 확인할 수 있다.

# Netscape HTTP Cookie File
# http://curl.haxx.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.

xxx.xxx.xxx FALSE   /   FALSE   1499676993  csrftoken   97154f650b5e4e5a63fbec267072cc38
xxx.xxx.xxx FALSE   /   FALSE   0   sessionid   a37046e1944553ee8dc2af0bc5c483fc

이제 위 sessionid cookie를 이용하여 /bom/register/ page에 접근하면 된다.

# curl -b cookie.txt http://xxx.xxx.xxx/bom/register/

이제 위의 response가 200인지만 확인하면 된다.

TIP

firefox나 chrome에는 개발자 도구(F12)등이 포함되어 있다. 이곳에서 request, response의 header/body값과 cookie값등을 모두 확인할 수 있다.

이를 이용하면 실제 브라우져와 동일하게 http request를 요청하게 처리할 수 있을것이다. 위의 경우 curl로 진행했지만, python httplib 도 header와 body, cookie을 동일하게 처리하면 된다.

Script

위 내용을 토대로 http://xxx.xxx.xxx 사이트의 로그인 시도를 주기적으로 시도하여 문제 발생시(status code 400 이상일 경우) was와 mysql을 재시작하는 스크립트를 작성하였다. 해당 스크립트는 일반 사용자 계정으로 crontab에 등록되어 5분간격으로 실행된다.

crontab의 아래와 같이 오분마다 스크립트를 실행하도록 추가하였다.

$ crontab -e
*/5 * * * * ~/check_login.sh 2>&1

check_login.sh 스크립트 내용은 아래와 같다.

#!/bin/bash

USERID="sungmin" <- 사이트의 로그인 아이디를 입력
USERPW="sungmin" <- 사이트의 로그인 패스워드를 입력
DATE=$(date +%m%d%H%M)
LOG="~/log/check_login_${DATE}.log"

curl -c cookie.txt http://xxx.xxx.xxx/login/ -s > /dev/null

TOKEN=$(grep csrftoken cookie.txt | awk '{print $NF}')

curl -b cookie.txt -d "csrfmiddlewaretoken=${TOKEN}&username=${USERID}&password=${USERPW}" http://xxx.xxx.xxx/login/ -c cookie.txt

RESULT=$(curl -b cookie.txt http://xxx.xxx.xxx/bom/register/ -I -s | head -n1 | awk '{print $2}')
echo ${RESULT}

if [ ${RESULT} -gt 399 ]; then <- status code 결과 값이 400 이상일 경우 아래 명령을 실행
        echo "## Web Status code" > $LOG
        echo ${RESULT} >> $LOG
        echo "## Memory Status" >> $LOG
        free -m >> $LOG
        echo "## Process Check" >> $LOG
        ps -eo pid,rsz,vsz,cmd | grep python | grep -v grep >> $LOG
        echo "## Load average" >> $LOG
        w | head -n1 >> $LOG
        echo "Nginx & WAS & MySQL Restart" >> $LOG
        sudo /etc/init.d/nginx restart >> $LOG 2>&1
        sleep 1
        sudo /etc/init.d/mysql restart >> $LOG 2>&1
        sleep 1
        sudo -u sungmin ~/script/runserver restart >> $LOG 2>&1
        sleep 1
        echo ${RESULT} >> $LOG 2>&1
        mail -s "Server Error & Process Restart" sungmin@xxx.xxx < $LOG
fi

log는 문제 발생 시간에 mysql과 was가 재시작되며 그 전의 메모리 사용률, was 프로세스의 메모리 사용률과 갯수, load average를 log 파일로 기록한다.(로그파일 위치 : ~/check_login/log/)

아래는 mysql을 종료하고 스크립트가 구동되어진 후 로그를 출력. 이후 서비스 정상 확인.

$ cat check_login.log
## Memory Status
             total       used       free     shared    buffers     cached
Mem:          1982        399       1583          0         23        251
-/+ buffers/cache:        124       1857
Swap:          952          0        952
## Process Check
 4277 24672  87860 /usr/bin/python manage.py runfcgi host=127.0.0.1 port=8080
 4278 23888  87860 /usr/bin/python manage.py runfcgi host=127.0.0.1 port=8080
 4279 23908  87860 /usr/bin/python manage.py runfcgi host=127.0.0.1 port=8080
 4280 23908  87860 /usr/bin/python manage.py runfcgi host=127.0.0.1 port=8080
 4281 46904 158356 /usr/bin/python manage.py runfcgi host=127.0.0.1 port=8080
 4282 23908  87860 /usr/bin/python manage.py runfcgi host=127.0.0.1 port=8080
## Load average
 02:52:05 up 5 min,  2 users,  load average: 0.10, 0.12, 0.06
## Nginx & WAS & MySQL Restart
sudo: unable to resolve host test
Restarting nginx: nginx.
sudo: unable to resolve host test
Stopping MySQL database server: mysqld.
Starting MySQL database server: mysqld ..
Checking for tables which need an upgrade, are corrupt or were
not closed cleanly..
Stop django server : .Done
Start django server : Done.

Let`s Encrypt 무료 인증서 설치 방법

Let’s Encrypt란?

Lets’ Encrypt는 HTTPS를 사용하기 위해 SSL을 구매해야 하는 부분이 HTTPS 보급에 방해된다고 생각해서 SSL을 무료로 제공해서 HTTPS를 보급하기 위해 작년 말에 만들어졌다.

초기에는 Mozilla, Cisco, Akamai, EFF, id entrust 등이 모여서 ISRG(Internet Security Research Group)라는 새로운 SSL 인증기관을 만들어서 올해 SSL을 무료로 제공하겠다고 발표했다.

지금은 이 Lets’ Encrypt에 Facebook, 워드프레스를 만드는 Automattic, shopify 등 많은 회사가 스폰서로 참여하고 있다.

Getting the Let’s Encrypt client

let’s encrypt는 web server에 ssl 설정을 가장 단순하게 자동적으로 설정할 수 있다.

이를 위해서 client를 받아야되는데 github repository(https://github.com/certbot/certbot)에서 다운 받을 수 있다. git이 설치되어 있지 않다면 아래 명령어로 설치를 진행한다.

$ sudo apt-get install git
$ sudo yum install git

git 설치가 되었다면 아래 명령어로 다운 받는다.

$ git clone https://github.com/letsencrypt/letsencrypt

다운되어진 letsencrypts 디렉토리로 이동한다.

$ cd letsencrypt

client와 함께 제공되는 letsencrypt-auto 스크립트를 구동하여 인증서를 생성할 수 있다. 인증서 생성은 관리자 권한이 필요하기에 sudo를 사용한다. 구동하기 전 let’s encrypt 의 인증 방식인 Standalone plugin 은 서버 인증을 위해서 80포트를 이용하기 때문에 nginx, apache 와 같이 80 포트를 이용하는 서비스가 있다면 서비를 일시적으로 중지해야 한다.

$ sudo /etc/init.d/nginx restart
$ sudo netstat -nlp | grep 80

80 포트로 운영되는 서비스가 없는 것을 확인하였다면 아래 스크립트를 실행하여 인증서를 생성한다.

$ sudo ./letsencrypt-auto certonly --standlone

관련 패키지가 설치된 뒤 이메일 주소를 입력하는 창을 확인 할 수 있다. 긴급 공지나 키를 복구하기 위해 사용되는 이메일 주소를 입력한다.

이용약관에 동의한다. Agree Enter

마지막으로 도메인 주소를 입력한다. 도메인 주소는 FQDN 표기법으로 입력한다.

정상적으로 생성이 완료될 경우 congratulations! 메시지와 함께 인증서가 생성된 것을 확인할 수 있다. 아래 내용을 보면 인증서가 2016-10-20 에 만료된다고 나와 있다. Let’s encrypt의 인증서는 90일 동안 유효한 인증서이므로 90일마다 새로 갱신을 해야 한다. 자동 갱신 방법에 대해서는 아래에서 추가하도록 하겠다.

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/example.com/fullchain.pem. Your cert
   will expire on 2016-10-20. To obtain a new or tweaked version of
   this certificate in the future, simply run letsencrypt-auto again.
   To non-interactively renew *all* of your certificates, run
   "letsencrypt-auto renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

인증서가 생성된 해당 경로로 이동하면 아래와 같은 인증서를 볼 수 있다.

  • cert.pem : 도메인 인증서
  • chain.pem : Let’s Encrypt chain 인증서
  • fullchain.pem : cert.pem 과 chain 인증서 합본
  • privkey.pem : 개인키

Nginx SSL 설정

nginx 웹서버에 경우 fullchain.pem과 privkey.pem 인증서를 사용한다.

신규 vhost 파일을 생성한다.

$ sudo vi /etc/nginx/site-available/example

443 포트를 listen 하며 server_name에 도메인 주소를 입력합니다. 아래 설정을 참고하여 생성한다.

listen 443 ssl;

server_name example.com www.example.com;

ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';

location ~ /.well-known {
        allow all;
}

추가적으로 80 포트에서 들어오는 주소를 443 https로 보내주는 설정을 추가할 수 있다.

server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

설정이 완료되었다면 nginx configtest 를 진행한 뒤 문제가 없을 경우 restart하여 설정을 적용한다.

$ sudo /etc/init.d/nginx configtest
Testing nginx configuration: nginx.
$ sudo /etc/init.d/nginx restart

Let’s Encrypt 인증서 자동 갱신

위에서 정상적으로 인증서를 발급하게 되면 만료기간이 언제인지 확인 할 수 있다. 작성한 내용이지만 90일 동안 인증서가 유효하기에 90일이 지나면 새로 갱신을 해야 한다. 우선 갱신하는 방법은 아래와 같다.

위에서 받은 client 폴더로 이동하여 letsencrypt-auto 스크립트로 갱신이 가능하다.

$ sudo ./letsencrypt-auto renew

아래와 같이 스크립트를 만들어 매달 1일날 해당 스크립트를 구동하여 자동으로 갱신이 되도록 crontab에 설정하였다. 유효기간이 30일 남았을 때부터 갱신이 가능하기에 매주 일요일 새벽 2시 해당 스크립트를 구동한다.

# vi /etc/nginx/letsencrypt/renew.sh

#!/bin/sh
/etc/init.d/nginx stop
if ! /etc/nginx/letsencrypt/letsencrypt-auto renew > /var/log/letsencrypt-renew.log 2>&1 ; then
        echo Automated renew failed;
        cat /var/log/letsencrypt-renew.log
        exit 1
fi
/etc/init.d/nginx start
# crontab -l
0 2 * * 0 /etc/nginx/letsencrypt/renew.sh

DHCP Relay를 이용하여 여러 네트워크 대역의 DHCP 서비스

개요

일반적으로 DHCP는 동일 네트워크(subnet)에서 작동한다. Client가 DHCP 요청시 dhcp discover message를 broadcast로 보내기때문이다. 그래서 다른 네트워크(subnet)의 dhcp dicover message는 dhcp server로 전달되지 않는다. 그럼 여러 네트워크에서 dhcp를 사용하려면 각 네트워크 대역마다 dhcp server를 두어야만 하는가? 이때 dhcp relay를 이용하면 하나의 dhcp server에서 여러 네트워크를 서비스할 수 있다. dhcp relay는 dhcp 요청을 다른 네트워크 대역에 있는 dhcp server로 중계(relay)하는 역할을 한다. 각 대역마다 dhcp relay를 두어 하나의 dhcp server로 relay하게끔 설정하면 된다.

구성

ntwork vendor사마다 이름은 다르지만 relay 기능들이 존재한다. (예를 들면 cisco의 ip-helper) 우리는 debian jessie os 위에 isc dhcp(https://www.isc.org/downloads/dhcp/)를 얹어 구성할 것이다. 네트워크의 구성은 아래의 그림과 같다.

  • A 네트워크(192.168.0.0/24)와 B 네트워크(192.168.1.0/24), 총 2개의 네트워크 구성이다.
  • A 네트워크에 DHCP Server가 구동된다.
  • DHCP Relay는 B 네트워크의 DHCP 요청을 A 네트워크의 DHCP Server로 중계할것이다.

DHCP Server 설치/설정

DHCP Server에서 아래와 같이 isc-dhcp-server package를 설치한다.

# apt-get install isc-dhcp-server

DHCP 설정은 다른 옵션값을 배제하고 최대한 단순하게 했다. 네트워크의 range만 설정했다.

# vi /etc/dhcp/dhcpd.conf
subnet 192.168.0.0 netmask 255.255.255.0 {
    range 192.168.0.10 192.168.0.20;
}
subnet 192.168.1.0 netmask 255.255.255.0 {
    range 192.168.1.10 192.168.1.20;
}

DHCP 서버는 A 네트워크 뿐아니라 B 네트워크의 DHCP 설정값을 모두 가지고 있다. DHCP Relay에 DHCP Server로 전달는 설정뿐 별다른 설정이 없다. isc-dhcp-server를 설치한적이 있다면 별다른게 없음을 알수있다. 자기 네트워크이외 다른 네트워크의 설정만 추가되었을 뿐이다.

이제 dhcp daemon를 restart한다.

# systemctl restart isc-dhcp-server
# ps -ef|grep dhcrelay

DHCP relay 설치/설정

DHCP Relay Server에서 isc-dhcp-relay package를 설치한다.

# apt-get install isc-dhcp-relay

이제 DHCP Server로 relay하는 설정을 진행한다.

# vi /etc/default/isc-dhcp-relay
SERVERS="192.168.0.1"
INTERFACES="eth0 eth1"
  • SERVERS : relay할 서버를 설정한다. 보면 S가 붙어있다. 여러개의 DHCP 서버로 relay 가능하다. (space-seperated)
  • INTERFACES : relay service를 할 interface를 설정한다. Manpage에는 나와있지 않지만 추가로 DHCP와 통신할 interface도 넣어야한다. 따라서 2개의 interface를 모두 설정하였다.

이제 relay daemon를 restart한다.

# systemctl restart isc-dhcp-relay
# ps -ef|grep dhcrelay

DHCP Client 확인

이제 DHCP Client에서 제대로 IP를 받아오는지 확인하면 된다. 제대로 받아왔다면 dhcp server에서는 아래와 같은 log를 확인할수 있을것이다.

# tail /var/log/daemon.log
dhcpd: DHCPDISCOVER from 00:16:3e:ee:92:cd via 192.168.0.2
dhcpd: DHCPOFFER on 192.168.1.10 to 00:16:3e:ee:92:cd via 192.168.0.2
dhcpd: DHCPREQUEST for 192.168.1.10 (192.168.0.1) from 00:16:3e:ee:92:cd via 192.168.0.2
dhcpd: DHCPACK on 192.168.1.10 to 00:16:3e:ee:92:cd via 192.168.0.2

작동 방법

DHCP는 Discover, Offer, Request, Ack 4개의 Message로 구성되어 있다. (참조 : http://www.netmanias.com/ko/post/blog/5348/dhcp-ip-allocation-network-protocol/understanding-the-basic-operations-of-dhcp) 그럼 궁금증이 생긴다. Discover와 Request같은 broadcast는 relay server가 받은 그대로 다시 broadcast하는가? DHCP Relay는 DHCP Server와 unicast로만 message를 주고 받는다. 그래서 위의 SERVERS 설정으로 주고 받을 ip를 설정한것이다.

  • client의 dhcp discover message는 broadcast로 dhcp relay에 도착된다. dhcp relay는 이 discover를 unicast로 dhcp server에 전달한다.
  • dhcp server는 offer message를 unicast로 dhcp relay에 전달한다. dhcp relay는 이 offer를 broadcast로 client에 전달한다.

Trouble Shutting

만약 문제가 발생할 경우 -q 옵션을 지워 quite mode를 끄고, -f 옵션으로 daemon을 foreground로 띄우는게 좋다. 나오는 message를 잘 확인해보자. 물론 man page는 필수다. (man dhcpd, man dhcrelay)

xen에서의 checksum error

위의 dhcp log의 mac을 보면 눈치했을수도 있지만, 위 테스트를 xen에서 진행하였다. 그런데 relay에서 전달된 message가 dhcp server에서 받지 못하는 현상이 나타났다. 원인은 dhcp server nic의 udp checksum error였다. vm의 nic module을 vif에서 e1000(emulated)로 변경후엔 문제없이 진행되었다. 구글링을 해보니 대부분 workaround로 ethool을 이용하여 checksum를 끄라고 나와있는데 내 경우엔 작동되지 않았다.

SR-IOV on XEN

개요

SR-IOV?

  • SR-IOV : Single Root-Input Output Virtualization
  • 물리 PCIe 장치를 여러 개의 PCIe 장치로 보이게 하는 PCI-SIG(http://www.pcisig.com/)에서 제정한 표준 스펙문서
  • SR-IOV는 Physical functions(PFs)와 Virtual Functions(VFs)로 나누어짐
  • PFs : Full-featured PCIe functions (configuration and data functions)
  • VFs : lightweight PCIe functions (only data functions)
  • PCIe 장치는 최대 256 VFs까지 가질수 있으나 각 VF는 실제 하드웨어 리소스가 필요하기 때문에 실제적인 최대값은 64VFs
  • 제한점
    • VF는 PF와 같은 장치로만 사용 가능
    • VF는 PF와 같은 네트워크 대역이어야 함
  • 조건
    • SR-IOV를 지원하는 mainboard(bios)와 CPU (intel VT-D or AMD iommu)
    • SR-IOV를 지원하는 NIC (e.g. Intel 82576)
    • SR-IOV를 지원하는 Hypervisor, Dom0, DomU

Xennet vs. SR-IOV

설정

지원확인

먼저 서버의 CMOS 에 들어가서 Intel VT-d 가 켜져 있는지 확인하라. VT-d 가 없으면 SR-IOV를 사용할 수 없다.

dom0# xm dmesg
(XEN) Intel VT-d Snoop Control enabled.
(XEN) Intel VT-d Dom0 DMA Passthrough not enabled.
(XEN) Intel VT-d Queued Invalidation enabled.
(XEN) Intel VT-d Interrupt Remapping enabled.
(XEN) Intel VT-d Shared EPT tables not enabled.
(XEN) I/O virtualisation enabled

NIC 역시 SR-IOV를 지원해야 한다. 아래 Intel 82576 NIC은 SR-IOV를 지원하는 NIC이다. linux module 이름은 igb 이다.

dom0# modinfo igb
filename:       /lib/modules/2.6.32.39-175.xendom0.el6.x86_64/kernel/drivers/net/igb/igb.ko
version:        1.3.16-k2
license:        GPL
description:    Intel(R) Gigabit Ethernet Network Driver
author:         Intel Corporation, <e1000-devel@lists.sourceforge.net>
srcversion:     67CCD4CBBFB5F99C69546C2
alias:          pci:v00008086d000010D6sv*sd*bc*sc*i*
alias:          pci:v00008086d000010A9sv*sd*bc*sc*i*
alias:          pci:v00008086d000010A7sv*sd*bc*sc*i*
alias:          pci:v00008086d000010E8sv*sd*bc*sc*i*
alias:          pci:v00008086d0000150Dsv*sd*bc*sc*i*
alias:          pci:v00008086d000010E7sv*sd*bc*sc*i*
alias:          pci:v00008086d000010E6sv*sd*bc*sc*i*
alias:          pci:v00008086d00001518sv*sd*bc*sc*i*
alias:          pci:v00008086d0000150Asv*sd*bc*sc*i*
alias:          pci:v00008086d000010C9sv*sd*bc*sc*i*
depends:        dca
vermagic:       2.6.32.39-175.xendom0.el6.x86_64 SMP mod_unload
parm:           max_vfs:Maximum number of virtual functions to allocate per physical function (uint)

마지막 parm: 라인을 주목하자. 이것이 바로 SR-IOV의 기능을 설명한다. max_vfs option을 주어 몇 개의 VF를 만들지 설정할 수 있다.

Dom0 설정

GRUB_CMDLINE_LINUX_DEFAULTpci_pt_e820_access=on, GRUB_CMDLINE_XEN_DEFAULTiommu=1 msi=1 추가하고 grub를 업데이트(update-grub)한다.

dom0# vi /etc/default/grub
GRUB_CMDLINE_LINUX_DEFAULT="quiet pci_pt_e820_access=on"
GRUB_CMDLINE_XEN_DEFAULT="dom0_mem=4G,max:4G iommu=1 msi=1"
dom0# update-grub
  • iommu=1 : enables SR-IOV
  • msi=1 : enables MSI-X interrupts
  • pci_pt_e820_access=on : enables direct assignment of a PCIe device to a Virtual Machine

그리고 아래와 같이 modprobe를 설정한다. igb driver를 loading할때 7개의 VF를 생성하고,

dom0# vi /etc/modprobe.d/igb.conf
options igb max_vfs=7
dom0# vi /etc/modprobe.d/igbvf-blacklist.conf
blacklist igbvf
dom0# update-initramfs -k `uname -r` -t -u
  • options igb max_vfs=7 : igb driver를 loading할 때 VF를 7개 생성
  • blacklist igbvf : igbvf는 domU에서 VF를 사용하기 위해서 loading하는 module이다. 따라서 dom0에서는 blacklist에 올려놓아 loading하지 않도록 한다.

이제 SR-IOV 설정이 끝났다. 리부팅한다.

dom0# lspci |grep Eth
01:00.0 Ethernet controller: Intel Corporation 82575EB Gigabit Network Connection (rev 02)
01:00.1 Ethernet controller: Intel Corporation 82575EB Gigabit Network Connection (rev 02)
03:00.0 Ethernet controller: Intel Corporation 82576 Gigabit Network Connection (rev 01)
03:00.1 Ethernet controller: Intel Corporation 82576 Gigabit Network Connection (rev 01)
03:10.0 Ethernet controller: Intel Corporation 82576 Virtual Function (rev 01)
03:10.1 Ethernet controller: Intel Corporation 82576 Virtual Function (rev 01)
03:10.2 Ethernet controller: Intel Corporation 82576 Virtual Function (rev 01)
03:10.3 Ethernet controller: Intel Corporation 82576 Virtual Function (rev 01)
03:10.4 Ethernet controller: Intel Corporation 82576 Virtual Function (rev 01)
03:10.5 Ethernet controller: Intel Corporation 82576 Virtual Function (rev 01)
03:10.6 Ethernet controller: Intel Corporation 82576 Virtual Function (rev 01)
03:10.7 Ethernet controller: Intel Corporation 82576 Virtual Function (rev 01)
03:11.0 Ethernet controller: Intel Corporation 82576 Virtual Function (rev 01)
03:11.1 Ethernet controller: Intel Corporation 82576 Virtual Function (rev 01)
03:11.2 Ethernet controller: Intel Corporation 82576 Virtual Function (rev 01)
03:11.3 Ethernet controller: Intel Corporation 82576 Virtual Function (rev 01)
03:11.4 Ethernet controller: Intel Corporation 82576 Virtual Function (rev 01)
03:11.5 Ethernet controller: Intel Corporation 82576 Virtual Function (rev 01)

Virtual function이 14개 생성된 것을 볼 수 있다. 82576 NIC이 두 개이고, max_vfs=7으로 했으므로 2*7 = 14개 생성된 것이다. 이것은 dmesg에서 보다 더 잘 볼 수 있다.

dom0# dmesg |grep -i vfs
igb 0000:03:00.0: 7 vfs allocated
igb 0000:03:00.1: 7 vfs allocated

이제 domU가 PCIe 장치를 바로 사용할 수 있도록 설정하자.

dom0# vi /etc/modprobe.d/pciback.conf
alias pciback xen-pciback
options pciback hide=(03:10.0)(03:10.1)(03:10.2)(03:10.3)(03:10.4)(03:10.5)(03:10.6)(03:10.7)(03:11.0)(03:11.1)(03:11.2)(03:11.3)(03:11.4)(03:11.5)

위 내용은 pciback module을 올릴 때 VFs에 해당하는 장치를 숨긴다. 실제 module이름은 xen-pciback이고 alias로 pciback을 설정하였다. 그리고 hide option을 통해 VFs 장치를 나열하였다.

pciback module을 올려보자.

dom0# modprobe pciback
dom0# dmesg
....
[  134.962573] pciback 0000:03:10.0: seizing device
[  134.962583] pciback 0000:03:10.2: seizing device
[  134.962592] pciback 0000:03:10.4: seizing device
[  134.962597] pciback 0000:03:10.6: seizing device
[  134.962602] pciback 0000:03:11.0: seizing device
[  134.962607] pciback 0000:03:11.2: seizing device
[  134.962612] pciback 0000:03:11.4: seizing device
[  134.962617] pciback 0000:03:10.1: seizing device
[  134.962621] pciback 0000:03:10.3: seizing device
[  134.962627] pciback 0000:03:10.5: seizing device
[  134.962632] pciback 0000:03:10.7: seizing device
[  134.962636] pciback 0000:03:11.1: seizing device
[  134.962641] pciback 0000:03:11.3: seizing device
[  134.962646] pciback 0000:03:11.5: seizing device
[  134.962707] pciback 0000:03:11.5: enabling device (0000 -> 0002)
[  134.962750] pciback 0000:03:11.3: enabling device (0000 -> 0002)
[  134.962787] pciback 0000:03:11.1: enabling device (0000 -> 0002)
[  134.962823] pciback 0000:03:10.7: enabling device (0000 -> 0002)
[  134.962865] pciback 0000:03:10.5: enabling device (0000 -> 0002)
[  134.962908] pciback 0000:03:10.3: enabling device (0000 -> 0002)
[  134.962944] pciback 0000:03:10.1: enabling device (0000 -> 0002)
[  134.962978] pciback 0000:03:11.4: enabling device (0000 -> 0002)
[  134.963011] pciback 0000:03:11.2: enabling device (0000 -> 0002)
[  134.963045] pciback 0000:03:11.0: enabling device (0000 -> 0002)
[  134.963080] pciback 0000:03:10.6: enabling device (0000 -> 0002)
[  134.963116] pciback 0000:03:10.4: enabling device (0000 -> 0002)
[  134.963150] pciback 0000:03:10.2: enabling device (0000 -> 0002)
[  134.963183] pciback 0000:03:10.0: enabling device (0000 -> 0002)
[  134.963240] xen-pciback: backend is vpci

이제 domU가 SR-IOV를 사용할 준비가 되었다. 다음 명령으로 어느 pci장치를 이용할 수 있는 지 알 수 있다.

dom0# xm pci-list-assignable-devices |sort -n  (xl pci-assignable-list)
xl pci-list-assignable-devices
0000:03:10.0
0000:03:10.1
0000:03:10.2
0000:03:10.3
0000:03:10.4
0000:03:10.5
0000:03:10.6
0000:03:10.7
0000:03:11.0
0000:03:11.1
0000:03:11.2
0000:03:11.3
0000:03:11.4
0000:03:11.5

DomU 설정

domU config파일에 pci=[‘03:10.0’] 을 추가하자.

#vif=["mac=00:16:3e:13:d1:31,bridge=xenbr0"]
pci=["03:10.0"]

vif 부분을 주석처리하였다. 즉, xen netback을 사용하지 않고 pci through 장치를 사용하겠다는 것이다.

VM을 구동하여 정상적으로 잡혀 있는지 확인한다.

domU#  lspci
00:00.0 Host bridge: Intel Corporation 440FX - 82441FX PMC [Natoma] (rev 02)
00:01.0 ISA bridge: Intel Corporation 82371SB PIIX3 ISA [Natoma/Triton II]
00:01.1 IDE interface: Intel Corporation 82371SB PIIX3 IDE [Natoma/Triton II]
00:01.2 USB Controller: Intel Corporation 82371SB PIIX3 USB [Natoma/Triton II] (rev 01)
00:01.3 Bridge: Intel Corporation 82371AB/EB/MB PIIX4 ACPI (rev 01)
00:02.0 VGA compatible controller: Technical Corp. Device 1111
00:03.0 Unassigned class [ff80]: XenSource, Inc. Xen Platform Device (rev 01)
00:05.0 Ethernet controller: Intel Corporation 82576 Virtual Function (rev 01)

맨 마지막 라인을 보면 pci passthrough가 된 것을 볼 수 있다. intel 82576 NIC의 VF가 보인다.

이제 DomU의 network를 설정하면 된다.

troubleshooting

DomU Kernel Version

Linux Kernel 2.6.37부터 PCI frontend driver가 SR-IOV를 지원한다.

Dom0에서 VF에 Mac 부여

VF에 Mac주소를 할당하지 않고 VM을 부팅하면 VM이 mac 주소가 없다고 하여 NIC자체를 거부한다. 따라서 dom0에 아래와 같이 할당할 VF에 대한 Mac을 설정해주고 domu를 부팅해야 한다.

dom0# ip link set eth0 vf 0 mac 00:1e:67:65:93:01

이전과 이렇게 다른 이유는 xen version 차이(4.1.4 vs. 4.4-unstable) 때문일 것이다.

특정 서버의 경우 iommu 옵션 변경 필요

일부 수퍼마이크로 서버의 경우 Xen grub option에 iommu=1 로 주고 부팅하면 다음과 같은 에러가 발생한다.

(XEN) DMAR_IQA_REG = 7f79d000
(XEN) DMAR_IQH_REG = 0
(XEN) DMAR_IQT_REG = 20
(XEN)
(XEN) ****************************************
(XEN) Panic on CPU 0:
(XEN) queue invalidate wait descriptor was not executed
(XEN) ****************************************
(XEN)
(XEN) Reboot in five seconds...

이 경우 queue invalidate를 하지 않도록 수정해야 한다. iommu=no-qinval을 주고 부팅하면 된다.