プロメモグラム

誰が見てもわかるような文章を目指す

Ubuntu 18.04 で KVM + OpenvSwitch(NAPT)

いくつかのサイトを参考に作成.

環境

OS

  • ホスト:Ubuntu Desktop (18.04)
  • ゲスト:Ubuntu Server (18.04)

Hypervisor

やりたいこと

正しい書き方かわからないけど概要はこんな感じ.

f:id:zia_glass:20181013044505p:plain

KVMの設定

基本は省略する.defaultネットワークを停止させ,自動起動を無効化する.

以下のコマンドでLinux標準ブリッジを無効化する.

rmmod bridge

OpenVSwitchの設定

インストール

apt install openvswitch-switch
apt install openvswitch-common
apt install python-openvswitch
apt install python-netifaces

Ubuntu 18.04はnetplanでネットワークされるが,OVSに非対応なので従来の設定ファイルを行う. (netplanの設定でmatchというキーを使えばIPアドレスを付加できたが接続は未検証) まずは,/etc/netplan/に存在するファイルの中身を全てコメントアウトする.

次に以下のパッケージをインストールする.

apt install ifupdown

/etc/network/interfacesを編集.ブリッジの設定をここに書く.

auto lo
iface lo inet loopback

auto eth0 
iface eth0 inet dhcp

auto br0 
iface br0 inet static
address 192.168.0.1
netmask 255.255.255.0
network 192.168.0.0
ovs_type OVSBridge

保存したら設定を適用.

ifdown br0
ifup br0

ブリッジ設定ファイルを作成.

<network>
  <name>br0</name>
  <forward mode='bridge'/>
  <bridge name='br0'/>
  <virtualport type='openvswitch'/>
</network>

設定ファイルを適用.

virsh net-define br0.xml
virsh net-start br0
virsh net-autostart br0

作成したブリッジを仮想マシンに設定. この際にもともとあるmacvtapを選ばないように注意.

f:id:zia_glass:20181013044530p:plain

ufwフォワーディング設定

/etc/ufw/before.rules
*filterと書かれている行の前に以下を記述.

*nat
:PREROUTING ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-F
-A POSTROUTING -s 192.168.0.0/24 -o eth0 -j MASQUERADE

COMMIT

/etc/ufw/sysctl.conf

net/ipv4/ip_forward=1

/etc/default/ufw

DEFAULT_FORWARD_POLICY="ACCEPT"

3つの設定を弄ったら,ufwを適用. ufwはデフォルトで全通信を遮断しインターネットに繋がらなくなるので注意(許可する設定をする必要があるけど省略).

ufw disable
ufw enable

うまく行っているか確認.

iptables -L

dnsmasqの設定

インストールとSystemdで起動.

apt install dnsmasq
systemctl enable dnsmasq
systemctl start dnsmasq

/etc/dnsmasq.conf

listen-address=192.168.0.1
dhcp-range=192.168.0.50,192.168.0.150,255.255.255.0,12h

ゲストOS側の設定

ゲストはnetplanで大丈夫.うまく行けばインターネットに繋がる.

network:
    version2:
        ethernet:
            ens3:
                dhcp4: true
                addresses: []

設定適用.

netplan apply

KVMでハマったところ

KVMでのハマった点をメモする. 以下の2つの現象が起きた.

  • クローン後にホスト名を変更できない
  • クローン後にDHCPが同じIPを排出する

環境

OS

  • Ubuntu Desktop 18.04(ホストOS)
  • Ubuntu Server 18.04(ゲストOS)

仮想環境

  • Compiled against library: libvirt 4.0.0
  • Using library: libvirt 4.0.0
  • Using API: QEMU 4.0.0
  • Running hypervisor: QEMU 2.11.1
  • virsh 4.0.0

前提

Ubuntu Server 18.04をインストール済みのイメージをvirt-cloneを用いてクローンした.

1. クローン後にホスト名を変更できない

問題

ホスト名を変更するhostnamectlを用いても再起動すると元のホスト名に戻ってしまう.

解決

cloud-initの設定(/etc/cloud/cloud.cfg)を変更することで解決した.

# 前:
preserve_hostname: false
# 後:
preserve_hostname: true

変更後hostnamectlによりホスト名を変更して再起動.

2. クローン後にDHCPが同じIPを排出する

問題

virsh editMACアドレスやUUIDが重複していないことを確認したにも関わらず, DHCPでリースされるIPアドレスが重複してしまう.

解決

根本的な原因がわかっていないが,設定をリセットするコマンドであるvirt-sysprepを利用することで, 別々のIPアドレスが振られるようになった.

virt-sysprepを実行するために ホスト側でlibguestfs-toolsをインストールする必要がある.

$ sudo apt install libguestfs-tools

インストール後,特定の設定をリセットする. もしかしたら無駄な設定もリセットしているかも.

$ sudo virt-sysprep -d [クローンしたマシン名] \
--enable dhcp-client, machine-id, net-hwaddr

Linux上のFirefoxでタイトルバーを隠す

LinuxFirefoxを使う上で、タイトルバーが無駄に伸びているのが気になる。 Chromeでは、タブとタイトルバーが一緒になっているので、それが決めてでChromeを使っていた。

環境

Firefoxのバージョンによって、方法が違うかもしれない。

操作

  1. アドレスバーにabout:configと入力し、設定画面に遷移。
  2. browser.tabs.drawInTitlebarと検索する。
  3. falseになっているので、trueにする。

結果

もともとの画面。タイトルバーが無駄にある f:id:zia_glass:20180602151557p:plain

タイトルバーがなくなりスッキリした。 f:id:zia_glass:20180602151604p:plain

Railsデプロイで困ったことメモ

調べながら解決していったので、自分の詰まったところとそのリンクのまとめに近い。

やっていること

  1. 規模が小さい趣味プログラムをサブディレクトリによって、複数のWebサービスを同一のサーバで起動させる。
  2. HTTPSの設定と、HTTPへのリクエストをHTTPSにリダイレクト。

環境

Rails側設定

/config/environments/production.rb

SSLの強制、/publicの公開、サイトのトップページをサブディレクトリに。

config.force_ssl = true
config.public_file_server.enabled = true
ENV["RAILS_RELATIVE_URL_ROOT"] = "/siteA"
Rails.application.config.relative_url_root = "/siteA"

/config.ru

サブディレクトリで起動するように変更。

require_relative 'config/environment'

map ActionController::Base.config.relative_url_root || "/" do
        run Rails.application
end

/config/puma.rb

ここで、nginxで振り分けるsockを生成する設定。 ポート番号もアプリごとに分ける。

_app_path = "#{File.expand_path("../..", __FILE__)}"
_app_name = File.basename(_app_path)
_home = ENV.fetch("HOME") { "/home/アプリケーションのディレクトリ" }
pidfile "#{_home}/run/#{_app_name}.pid"
bind "unix://#{_home}/run/#{_app_name}.sock"
daemonize true
directory _app_path
port        ENV.fetch("PORT") { ポート番号をアプリごとに設定 }

nginx側設定

設定ファイルをいじり、サブディレクトリをRailsのアプリケーションのポートに振り分ける。 Rails側のタイムアウトの時間設定はproxyのタイムアウトで設定するのが注意どころ。

upstream siteA {
  server unix:///home/~/run/~.sock fail_timeout=0;
}
upstream siteB {
  server unix:///home/~/run/~.sock fail_timeout=0;
}
server {
        listen 80;
        server_name www.example.com;
        return 301 https://$host$request_uri;
}
server {
        server_name www.example.com;
        listen 443 default ssl;
        ssl on;
        ssl_certificate     /etc/letsencrypt/live/www.example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/www.example.com/privkey.pem;
        location /pll {
                proxy_pass http://siteA;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header Host $http_host;
                proxy_set_header X-Forwarded-Proto https;
                proxy_redirect off;
        }
        location /hateconcat {
                proxy_pass http://siteB;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header Host $http_host;
                proxy_set_header X-Forwarded-Proto https;
                proxy_redirect off;
        }

        location / {
                root /var/www/html;
                index index.html;
        }

        error_page 500 502 503 504 /500.html;
        client_max_body_size 4G;
        proxy_connect_timeout       605;
        proxy_send_timeout          605;
        proxy_read_timeout          605;
        send_timeout                605;
        keepalive_timeout           605;
}

問題と解決のリンク

サブディレクトリへの展開

An unhandled lowlevel error occurred.

このようなエラーはSECRET_KEY_BASEが無いときに出ることが多いらしい。 起動時に環境変数を渡してあげる。

puma_error.logで確認できるのでわかりやすい。

Assetsをプリコンパイルしても、404エラーが出る。

プロダクション環境ではデフォルトでは、public/を公開しない。おそらくnginxなどに委ねることもできるからだと思う。

個人的に分かる範囲での正規表現

正規表現についての基本の基本を自分用にまとめる。
できるだけシンプルにまとめたかった。

他にもいろいろできるみたいだが、とりあえずはこれだけでもいろいろできそう。
ところどころ「・・・」とあるが、これは何か文字が入るという意味。

1文字の表し方

文字 概要
[・・・] 括弧内の文字の集合から1つ
[^・・・] 括弧内の文字以外の集合から1つ
[a-z] 小文字、大文字、数値など範囲した集合から1つ
. 任意の1文字
^ 行頭
$ 行末

バックスラッシュによる特殊な1文字

文字 概要
\d 数字(digit?)。[0-9]と等価。
\w 英数字(word?)
\s スペースまたはタブ
\t タブ
\n 改行
 [メタ文字] メタ文字そのものを文字として利用したいときに\でエスケープ

繰り返し系

文字 概要
{n} n回繰り返す
{m,n} mからn回繰り返す
+ 1回以上繰り返す
* 0回以上繰り返す
? 0回or1回。言い換えると省略可能な文字
+?, *? 最小マッチ。マッチする中で+や*の繰り返しが最小限のもの。

文字をまとめる括弧

文字 概要
(・・・) 文字をまとめる。言い換えると文字列。 キャプチャしたい時にまとまりとして使用。 キャプチャは後述。
(?:・・・) ()でまとめたいが、キャプチャしなくていいものを記録しない。
| 〜または〜。(abc|def)など。

キャプチャ

一致するものの中の一部を取り出すことができる。()を使って実現する。

# 言語100本ノックの22問目より。
import sys
import re

lines = ["[[Category:イギリス|*]]",
"[[Category:英連邦王国|*]]",
"[[Category:G8加盟国]]",
"[[Category:欧州連合加盟国]]",
"[[Category:海洋国家]]",
"[[Category:君主国]]",
"[[Category:島国|くれいとふりてん]]",
"[[Category:1801年に設立された州・地域]]"]


pattern = r'\[\[Category:(.*?)(\|.*)?\]\]'
for line in lines:
    for match in re.finditer(pattern, line):
        print(match.group(1))

()を2回使っているが、1つ目の括弧の中身がほしいので、group(1)で取り出している。
取り出し方は言語によって異なると思うが、大体は、1つ目、2つ目という感じで取れそう。

メモ化とline_profilerによる計測

アルゴリズムの勉強などをあまりしていなかったので,メモ化という言葉は就活なんかでの技術試験で知るのが初めてだった.
メモ化自体はそこまで難しいことではなく,何度も同じ計算をしないで,一度計算したものをうまく保存することで計算結果を再利用するというものである.

例として,フィボナッチ数列がよく使われる.
フィボナッチ数列は,fib(n) = fib(n-1) + fib(n-2)で定義されるため,fib(k)が何度も呼び出されることになる.
ここで,メモ化を用いることで,一度計算したfib(k)を保存し,再利用する.

これを,時間計測ツールであるline_profilerを用いて,計測した.

# 通常のフィボナッチ数列
def normal_fib(n):
    if n == 0 or n == 1 :
        return n
    else:
        return normal_fib(n-1) + normal_fib(n-2)

# メモするフィボナッチ数列
memo = [0 for x in range(100)]
def memo_fib(n):
    if n < 2 :
        return n
    elif memo[n] != 0:
        return memo[n]
    else:
        memo[n] = memo_fib(n-1) + memo_fib(n-2)
        return memo[n]

@profile
def main():
    print(memo_fib(20))
    print(normal_fib(20))

if __name__ == '__main__':
    print("Memoization!!!")
    main()
    print(memo)

fib(20)で比較した出力結果は以下のよう.

Memoization!!!
6765
6765
[0, 0, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Wrote profile results to memo.py.lprof
Timer unit: 1e-06 s

Total time: 0.015669 s
File: memo.py
Function: main at line 19

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    19                                           @profile
    20                                           def main():
    21         1         51.0     51.0      0.3      print(memo_fib(20))
    22         1      15618.0  15618.0     99.7      print(normal_fib(20))

当確率ではないランダム

サイコロのように当確率で目がでるのではなく、おみくじのように出現確率が変化するようなプログラムが必要になったので作成した。

実際にうまく言っているかを確認するため、ヒストグラムを表示させた。

weighted_random関数

  • 入力:Numpyの配列に重み付けをした数値を入れる(合計が1である必要はない)
  • 出力:ランダムな値(0~配列長-1)を返す
import numpy as np
import matplotlib.pyplot as plt

# 入力データ(それぞれ、10%,30%,60%)
data = np.array([0.1, 0.3, 0.6])

# 関数
def weighted_random(data):
    # 累積
    culm = [0] * data.shape[0]
    total = np.sum(data)
    for i in range(data.shape[0]):
        cuml[i] = np.sum(data[:i+1]) / total
    # 乱数から選択
    s = np.random.rand()
    m = 0
    for i in cuml:
        if s < i:
            break
        m += 1
    return m

# 表示
x = np.array([weighted_random(data) for _ in range(1000)])
plt.hist(x, rwidth=1.0, bins=3)

f:id:zia_glass:20171125214942p:plain

1000個の結果から見て大体100個、300個、600個程度の出力を得ている。 なんかもっと綺麗に書けないんだろうか。