"Articles are my own, not views of my employer"

就活や転職に役立てるためTips投稿や競プロwriteupを行いプレゼンスを高める自分用備忘録です

SSH経由でウェブサイトにアクセスする

使用例

SSH先のサーバからしか接続できないウェブサイト(例:学内サイト)をローカルから接続する.

方法

SSHでトンネルを掘る.

ssh -N -D 10080 user@hoge.com

ここで-fオプションをつけるとバックグランドで動かし続けることが可能.

10080は慣習で実際は任意.

ChromiumSSH経由で接続する.

chromium-browser --proxy-server="socks5://127.0.0.1:10080"

SOCKSプロトコルを使う. システム設定をせずともproxy-serverオプションで可能.

なお,すでに開いているブラウザが存在するときはすべて閉じてからコマンドを実行すること.

リモートからOSをインストールする

概要

通常,OSをインストールするにはCDやUSB等のメディアを直接PCに接続し,その場で設定を行う. もちろん,インストール途中のPCにはSSHできないためリモートからこれらの作業を行うことはできない.

本記事では,すでにSSH接続できる遠隔地のPCに対し,新たにOSをインストールし,デュアルブートの形で設定する方法を解説する.

おおまかな流れとしては以下のとおりである.

  1. 仮想マシンを使って新たにOSをインストールする
  2. 仮想マシンイメージをOSをインストールしたいパーティションに移す
  3. GRUBに登録する
  4. 起動する

背景

ローカル環境をLOCAL,リモートの環境をREMOTE,新たに作成する環境をNEWとする. なお,本記事とは直接関係ないが,中間ノードMIDが存在し,REMOTEはMID経由でしかアクセスできない. MID->REMOTEのアクセスは,mDNSを用いてIPアドレスの解決を行うことで実現している.

いま,REMOTEにSSHでき,システムが稼働中であるとする. REMOTEに存在する外部ストレージに新たにOSをインストールし,それを起動し,アクセスしたいとする. (同一ストレージデバイスの異なるパーティションに対しても今回は未検証だが,同一手順でできると思われる.この際はresize2fsで稼働中のファイルシステムを縮小し,新たなOS用の領域を確保する必要がある.) 現在稼働中のOSは/dev/sdaにあり,新たにインストールするOSは/dev/sdb1にインストールするとする.

実際の手順

仮想マシンのインストール

まずはREMOTE内でKVMを使って新たに仮想マシンを作成する. このとき,作成する仮想マシンのストレージ容量はあまり大きくならないように(例:8GB)する.

この例ではUbuntu16をインストールする.

REMOTE$ sudo virt-install \
--connect=qemu:///system \
-n ubuntu16 \
-r 1024 \
--disk path=new.img,size=8,format=raw \
--vcpus=1 \
--os-type linux \
--network network=default \
--nographics \
--extra-args='console=tty0 console=ttyS0,115200n8' \
--location http://ftp.jaist.ac.jp/pub/Linux/ubuntu/dists/xenial/main/installer-amd64/

ここで作成したOSが,新たに(仮想マシン上ではないく,ネイティブに)インストールするOSとなる. 注意点としては,

  1. swapを作成せずプライマリパーティションをひとつだけ作成する
  2. LVMを使わない

である.

なお,仮想マシンの保存先としてファイルを指定しているが,これを/dev/sdb1としてはいけない. インストール中に分かると思うが,仮想マシンでは仮想ストレージを作成するためにこれを直接起動することはできないからである.

インストールできたらシャットダウンする.

(オプション)シリアルコンソールを有効化する

Ubuntu16の場合はコンソール接続がそのままではできないので,インストール後に直接イメージファイルを編集して,コンソール接続できるようにする必要がある.

以下の記事に詳しい.

Ubuntu 16.04 LTS : KVM : 仮想マシン作成#1 : Server World

仮想マシンをセットアップ

仮想マシンを起動し,セットアップを行う.

SSHサーバのセットアップ

NEW$ sudo apt install openssh-server

.ssh/authorized_keysに公開鍵を登録する. なお,仮想マシンIPアドレスは,

REMOTE$ virsh net-dhcp-leases default

で分かる.

(オプション)mDNSの設定

MIDからhostname.localでアクセスできるようにmDNSの設定を行う.

NEW$ sudo apt install avahi-daemon

自動シャットダウンスクリプトを仕込む

後ほどNEWを仮想マシンを経由せずに起動することになる. この際,ネットワークにエラーが発生した場合SSHで接続できず,手出しができなくなってしまう. そのため一定時間で自動的にシャットダウンするスクリプトを仕込む. 直接NEWにアクセスできなくとも,WOLを利用してMIDからREMOTEを起動することはできるのである.

/etc/rc.localにスクリプトを作成し,置いておく. 例えば10分後に自動でシャットダウンをする動作を記述. もちろん,無事にSSHできた場合はこのプロセスを殺す.

ネットワークの設定

OSごと異なる環境に移すことになるのでネットワークにも不調が出る(出た). 今回の問題は/etc/network/interfacesの記述が間違っていたことだった.

現在,

NEW$ cat /etc/network/interfaces
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
auto ens2
iface ens2 inet dhcp

となっているものを,

NEW$ cat /etc/network/interfaces
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
auto eno1
iface eno1 inet dhcp

と変更する.

なお,"eno1"の見つけ方であるが,これは何度か起動,自動シャットダウンを繰り返して見つけることになる. 具体的には件のシャットダウンスクリプト内部に,dmesgをダンプして保存する処理を仕込み,シャットダウンする. その後REMOTEが起動したら,NEWのパーティションをマウントし,ダンプされたファイルを確認する.

dmesg中の以下のような箇所を発見する.

[    9.136448] e1000e 0000:00:19.0 eth0: registered PHC clock
[    9.136450] e1000e 0000:00:19.0 eth0: (PCI Express:2.5GT/s:Width x1) XX:XX:XX:XX:XX:XX
[    9.136452] e1000e 0000:00:19.0 eth0: Intel(R) PRO/1000 Network Connection
[    9.136478] e1000e 0000:00:19.0 eth0: MAC: 11, PHY: 12, PBA No: FFFFFF-0FF
[    9.136923] e1000e 0000:00:19.0 eno1: renamed from eth0

eth0が何にrenameされているか調べれば良い.

外部ストレージ(/dev/sdb)のセットアップ

fdisk等を使い,パーティション/dev/sdb1を作成する.

後から行う強引な方法のために/dev/sdb1をゼロ埋めで初期化する.

REMOTE$ sudo dd if=/dev/zero of=/dev/sdb1

仮想マシンの中身を外部ストレージに移す

まず新たに作成した仮想マシンを起動する.

先程作成した外部ストレージを仮想マシンにアタッチする.

REMOTE$ virsh attach-disk ubuntu16 /dev/sdb1 vdc --live

/dev/sda1の中身をddで外部ストレージにコピーする(!)

NEW$ sudo dd if=/dev/sda1 of=/dev/sdb1

NEWをシャットダウン.

ファイルシステムを直す.

NEWはもともと8GBのディスクに作られていたのでこのままではおかしなことになる. そこでfsckとresizeでファイルシステムを修正する.

REMOTE$ sudo e2fsck -f /dev/sdb1
REMOTE$ sudo resize2fs /dev/sdb2

GRUBをアップデート

新たに作成したOSをGRUBに登録する.

REMOTE$ sudo grub-mkdevicemap
REMOTE$ sudo update-grub

リブート

NEWを起動するようにgrub-rebootを使い調整する. 表示方法は前の記事を参照のこと.

その後rebootし,無事起動してくれることを祈る.

Ubuntuカーネルビルド手順

まとめ

Ubuntu Bionic(つまりUbuntu 18系の別名)をビルドする方法を記す. 基本的に Kernel/Compile - Community Help Wiki から多大に情報を得ているが,いくつかコメントを書いた.

なぜUbuntuカーネルをビルドするのか

もちろんオリジナルlinuxカーネルをそのままビルドしてインストールしても一応使える. Ubuntuカーネルはオリジナルのlinuxカーネルに多少の変更がしてあるらしい.

したがって,オリジナルのlinuxカーネルをビルドしてそのままインストールした場合,

  • グラフィックの様子がおかしい
  • ネットワークのバーチャルブリッジ(virbr0)とかが設定されない

などの不都合が起きる(起きた). その結果として仮想マシンが正常に起動しないなどのトラブルが発生する.

ビルド手順

ソースコードダウンロード

$ git clone git://kernel.ubuntu.com/ubuntu/ubuntu-bionic.git

いくつかコメント.

  • 上記URLの見つけ方はこれという決まった手順がない.
    • 自分の場合はubuntu-trustyのURLを見つけたのでこれをbionicに置き換えてみた.
  • 数十kB/sしかバンド幅がないのですごく時間がかかる.
  • apt sourceでやる方法もあるらしいがうまくいかずに時間を溶かしたので最初はこちらを推奨.

ビルド

$ cd ubuntu-bionic
$ git tag
$ git checkout Ubuntu-4.15.0-hoge-fuga
$ fakeroot debian/rules clean
$ AUTOBUILD=1 fakeroot debian/rules binary-generic
  • 普通は.configファイルをコピーしたり,make oldconfigを走らせたりするが不要.
    • binary-debsに応じて.configファイルが生成されるらしい.
    • したがってbinary-FLAVOURのように他のものを指定することが可能.
  • -jオプションでビルドスレッド数を指定する必要もない.
    • 自動で物理コア数分のスレッドが立ち上がる.
  • 上記サイトだとbinary-debsを指定しているがやめたほうがいい.
    • generic, lowlatency, cloud, serverその他すべてのフレーバーがビルドされてしまい,時間と空間を取られる.
    • binary-genericで明示的にフレーバーを指定する.
      • これでも物理6コアで20分かかる.

以上の操作でdebファイルが複数生成されているはずである.

$ ls ../linux-*deb
../linux-cloud-tools-4.15.0-23-generic_4.15.0-23.25_amd64.deb
../linux-headers-4.15.0-23-generic_4.15.0-23.25_amd64.deb
../linux-image-unsigned-4.15.0-23-generic_4.15.0-23.25_amd64.deb
../linux-modules-4.15.0-23-generic_4.15.0-23.25_amd64.deb
../linux-modules-extra-4.15.0-23-generic_4.15.0-23.25_amd64.deb
../linux-tools-4.15.0-23-generic_4.15.0-23.25_amd64.deb

インストール

これをインストールすれば完了である.

$ sudo apt install linux-headers-4.15.0-23
$ sudo dpkg -i linux-headers-4.15.0-23-generic_4.15.0-23.25_amd64.deb
$ sudo dpkg -i ../linux-modules-4.15.0-23-generic_4.15.0-23.25_amd64.deb
$ sudo dpkg -i ../linux-image-unsigned-4.15.0-23-generic_4.15.0-23.25_amd64.deb
$ sudo dpkg -i linux-modules-extra-4.15.0-23-generic_4.15.0-23.25_amd64.deb
  • 依存関係解消のために上の順序でインストールする必要がある.
    • とくに最初のaptに注意.

アンインストール

なおアンインストールは以下のようにする.

$ sudo dpkg -r linux-image-unsigned-4.15.0-23-generic

GRUBで次回起動カーネルを選択

次回どのカーネルを用いて起動するのかを,rebootする前に指定する. もちろんマシンが手元にあれば手で選ぶこともできるけど,これだとリモートからでもいける.

インストールされているカーネルのリストを取得

まず何がインストールされているか調べる. 汚いawkワンライナー

awk -F\' -v l=-1 -v ll=-1 \
    '/menuentry |submenu / {
    if ($1=="menuentry ") {l+=1; print l "\t" $2}
    else if ($1=="submenu ") {l+=1; ll=-1; print l "\t" $2}
    else {ll+=1; print "\t" l ">" ll "\t" $2}
    }' \
/boot/grub/grub.cfg

なお,以上のコマンドを冒頭にシバン!#/bin/shを付けて,/usr/sbinにgrub-listとでも名うっておいておくと楽.

出力例.

0 Ubuntu
1  Advanced options for Ubuntu
    1>0  Ubuntu, with Linux 4.15.0-29-generic
    1>1  Ubuntu, with Linux 4.15.0-29-generic (recovery mode)
    1>2  Ubuntu, with Linux 4.15.0-23-generic
    1>3  Ubuntu, with Linux 4.15.0-23-generic (recovery mode)
2  Memory test (memtest86+)
3  Memory test (memtest86+, serial console 115200)
4  Ubuntu 16.04.5 LTS (16.04) (on /dev/sdb2)
5  Advanced options for Ubuntu 16.04.5 LTS (16.04) (on /dev/sdb2)
    5>0  Ubuntu (on /dev/sdb2)
    5>1  Ubuntu, with Linux 4.4.0-134-generic (on /dev/sdb2)
    5>2  Ubuntu, with Linux 4.4.0-134-generic (recovery mode) (on /dev/sdb2)

次回起動カーネルの指定

major番号のみ,もしくは>でminor番号を追加して指定する.エスケープに注意.

例えば上の例で Ubuntu, with Linux 4.15.0-23-generic を指定したければ '1>2' とする.

$ sudo grub-reboot '1>2'

他のプログラムの標準出力にしたがって動くシェルスクリプト

要約

他のプログラムにパイプで繋ぐことで,そのプログラムの標準出力を受け取り,出力にしたがって動くシェルスクリプトの作成方法.

要件

今回はパフォーマンス測定のためのツールとして作成した. ここでは,プログラム中のある点での /proc/meminfo を知りたいとしよう. このとき,プログラムを改変して,fork等呼び出せばできるがあまりにめんどくさい. よってここで決められた文字列(たとえば"meminfo_record")を出力することで,これを記録するような別のスクリプトを動作させることを考える. このとき,記録用のシェルスクリプト自体に時間を取られないことが肝要である.

まとめると以下.

  • パイプで起動(e.g. $ target_proc | measure.sh
  • 入力は基本teeみたいに受け流し.ただし特定入力(e.g. "meminfo_record")については別コマンド実行.
  • ポーリングではなく,イベント駆動

答え

#!/bin/bash

while read line
do
    echo "$line"
    if [ "$line" = "meminfo_record" ]; then
        break
    fi
done

cat /proc/meminfo > meminfo.log

ハマった落とし穴

whileの書き方によって無限ポーリングしてハマる.

while :
do
    read line
    echo "$line"
    if [ "$line" = "meminfo_record" ]; then
        break
    fi
done

バックグラウンドで測定開始,後に終了させる

今回はmeminfo一発で終わりだが,ここでパフォーマンス測定プロセスを開始し,後にそれを終了させたいような場合を考える. このとき,

  1. バックグラウンド実行
  2. PID取得
  3. シグナル送信

の3つのテクニックが鍵となる.

バックグラウンド実行

some_command &

PID取得

some_command &
pid=$!

シグナル送信

ここではC-cみたいなの.

kill -INT $pid

Python3の正規表現を使ってランレングス符号化

正規表現についてちょっと学んだので「個人的メモ」.

やったのは正規表現

  • 後方参照
  • 連続マッチ

かな.

結局のところ,次々マッチさせたいならfinditerを使え,という結論(牛刀で切ります).

コツは()で繰り返し後方参照したいな,と思う部分をそのままfinditerにぶち込むこと. それをgroup()メソッドで取り出すこと.

import re

def compress(s):
    ans = ''
    m = re.finditer(r'(.)\1*', s)
    for i in m:
        seq = i.group()
        ans += str(len(seq))
        ans += seq[0]
    return ans

def decompress(s):
    m = re.finditer(r'\d+|[a-zA-Z]+', s)
    n_repeat = None
    ans = ''
    for i in m:
        if n_repeat is None:
            n_repeat = int(i.group())
        else:
            ans += i.group() * n_repeat
            n_repeat = None
    return ans

このとき,

>>> s = 'AAABBCDEFFFFF'
>>> s = compress(s)
>>> print(s)
3A2B1C1D1E5F
>>> s = decompress(s)
>>> print(s)
AAABBCDEFFFFF

になる.