kuroの覚え書き

96の個人的覚え書き

clusteringだけmatplotlibで

ヒートマップの描画ははじめてseabornを使ってみたのだが、噂通り細かい調節は難しい。
今回系統樹の枝の長さを入れたかったのだが、結局方法にたどり着けなかったので、系統樹の部分だけmatplotlibで描いてみる。
f:id:k-kuro:20200114145420p:plain
枝が自動で色がついていたり、まだ改良の余地はあるかな。

@app.route('/cluster', methods=['GET', 'POST'])
def cluster_index():
    user = g.user.name
    form=StatForm()
    dir = "./user/" + user + "/cluster"
    if os.path.exists(dir):
        shutil.rmtree(dir)
    if not os.path.exists(dir):
        os.makedirs(dir)
    input = dir + "/input.txt"
    output = dir + "/results.txt"

    if request.method == 'POST':
        if 'file' not in request.files:
            flash('No file part')
            return redirect(request.url)
        file = request.files['file']
        if file.filename == '':
            flash('No data input')
            return redirect(request.url)
        if file and allowed_file(file.filename):
            file.save(input)

    if os.path.exists(input):
        if form.fig_x.data:
        	fig_x=form.fig_x.data
        if form.fig_y.data:
        	fig_y=form.fig_y.data
        if form.fig_dpi.data:
        	fig_dpi=form.fig_dpi.data
        if form.c_method.data:
        	c_method=form.c_method.data
        if form.c_dist.data:
        	c_dist=form.c_dist.data
        if form.font_size.data:
        	f_size=form.font_size.data

        df = pd.read_csv(input, index_col=0)
        df_t = df.T
        header = df.columns
        from scipy.spatial.distance import pdist
        linkage_result = linkage(df_t, method=c_method, metric=c_dist)
        plt.figure(num=None, figsize=(fig_x,fig_y), dpi=fig_dpi, facecolor='w', edgecolor='k')
        dendrogram(linkage_result, leaf_font_size=f_size, labels=header)
        cluster_fig = dir + "/cluster_" + str(time.time()) + ".png"
        plt.savefig(cluster_fig)
        img_url = "../../static/" + cluster_fig

        return render_template('/tools/cluster.html', form=form, img_url=img_url)

    return render_template('/tools/cluster.html', form=form)

ClusteringとHeatmap

RNAseqのデータ解析で、とりあえずやっておくことといえば、遺伝子発現プロファイルをクラスタリングしてヒートマップを描くということだろうか。
R使いならRでやるんだろうけど、Rはあまり良く知らないため、これまで
MeVを使ってきた。最近のバージョンではWEBアプリ化されているらしいが、Javaスタンドアローン版を使っていた。
しかし、論文を書くとき、色々なソフトウェアの引用が面倒なので、できればPythonに一本化したいなということで、実装してみた。
f:id:k-kuro:20200113191327p:plain
データはI. Nookaewらの論文から拝借。

webアプリの該当部分抜粋。

@app.route('/heatmap', methods=['GET', 'POST'])
def heatmap_index():
    import numpy as np
    import pandas as pd
    import matplotlib
    import matplotlib.pyplot as plt
    import seaborn as sns
    user = g.user.name
    form=StatForm()
    dir = "./user/" + user + "/hm"
    if os.path.exists(dir):
        shutil.rmtree(dir)
    if not os.path.exists(dir):
        os.makedirs(dir)
    input = dir + "/input.txt"
    output = dir + "/results.txt"

    if request.method == 'POST':
        if 'file' not in request.files:
            flash('No file part')
            return redirect(request.url)
        file = request.files['file']
        if file.filename == '':
            flash('No data input')
            return redirect(request.url)
        if file and allowed_file(file.filename):
            file.save(input)

    if os.path.exists(input):
        if form.fig_x.data:
        	fig_x=form.fig_x.data
        if form.fig_y.data:
        	fig_y=form.fig_y.data
        if form.c_map.data:
        	c_map=form.c_map.data
        if form.c_method.data:
        	c_method=form.c_method.data
        if form.c_dist.data:
        	c_dist=form.c_dist.data
        if form.font_scale.data:
        	f_scale=form.font_scale.data
        rob='False'
        if form.rob.data:
            rob='True'
        sns.set()
        sns.set(style='whitegrid', font_scale=f_scale)
        df = pd.read_csv(input, index_col=0)
        header = df.columns
        gr = sns.clustermap(df[[h for h in header]], method=c_method, metric=c_dist, robust=rob, cmap=c_map, figsize=(fig_x,fig_y))
        denix = gr.dendrogram_row.reordered_ind
        dengid = [df.index[u] for u in denix]
        # gr.cax.set_position([.05, .2, .03, .45])
        # gr.fig.suptitle('heatmap', y=0.75)
        heat_fig = dir + "/heatmap_" + str(time.time()) + ".png"
        plt.savefig(heat_fig)
        img_url = "../../static/" + heat_fig

        return render_template('/tools/heatmap.html', form=form, img_url=img_url)

    return render_template('/tools/heatmap.html', form=form)

統計的検定をpythonで行う〜Tukey編

Student's T testはexcelでも特に難しくない。標準関数でできるからね。それに比べて多重検定はアドインとか手計算とか結構面倒なので、これをwebアプリでチョチョイとできるとすごくいい。というわけでまずはexcelで実施した場合。
f:id:k-kuro:20191231201921p:plain

https://www.amazon.co.jp/dp/4434211625/ref=cm_sw_em_r_mt_dp_U_y7YcEb6ERHBNK
この本(正確にはこの第3版)の付録のアドインで解析した結果。

検定統計量の絶対値が水準点より大きいと有意差があると判定される(アスタリスクのついているところ)。

さてこういうのをpythonで行うにはstatsmodels.stats.multicompモジュールの pairwise_tukeyhsd (Tukey's Honest Significant Difference)関数を用いるらしい。

$ python3
Python 3.6.5 (default, Apr 25 2018, 14:26:36) 
[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.39.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from statsmodels.stats.multicomp import pairwise_tukeyhsd
>>> import numpy as np
>>> A = np.array([1.375679545,0.686414272,0.557466114,1.256839102,1.123600968])
>>> B = np.array([14.04818061,5.350681195,13.82671427,9.978383591,11.54647591])
>>> C = np.array([0.867704526,1.059910525,0.954219068,1.061427443,0.882509883])
>>> D = np.array([0.748020814,0.455333729,0.601030484,0.275500152,0.741495253])
>>> E = np.array([4.937450411,9.076321974,8.039161896,5.332406264,5.118753692])
>>> F = np.array([0.482907748,0.372571868,0.929118102,0.997754185,0.79455389])
>>> data_arr = np.hstack( (A,B,C,D,E,F) )
>>> ind_arr = np.repeat(list('ABCDEF'),len(A))
>>> print(pairwise_tukeyhsd(data_arr,ind_arr))
 Multiple Comparison of Means - Tukey HSD, FWER=0.05 
=====================================================
group1 group2 meandiff p-adj   lower    upper  reject
-----------------------------------------------------
     A      B   9.9501  0.001   6.7007 13.1995   True
     A      C  -0.0348    0.9  -3.2843  3.2146  False
     A      D  -0.4357    0.9  -3.6851  2.8137  False
     A      E   5.5008  0.001   2.2514  8.7502   True
     A      F  -0.2846    0.9   -3.534  2.9648  False
     B      C  -9.9849  0.001 -13.2344 -6.7355   True
     B      D -10.3858  0.001 -13.6352 -7.1364   True
     B      E  -4.4493 0.0035  -7.6987 -1.1999   True
     B      F -10.2347  0.001 -13.4841 -6.9853   True
     C      D  -0.4009    0.9  -3.6503  2.8485  False
     C      E   5.5357  0.001   2.2862  8.7851   True
     C      F  -0.2498    0.9  -3.4992  2.9996  False
     D      E   5.9365  0.001   2.6871   9.186   True
     D      F   0.1511    0.9  -3.0983  3.4005  False
     E      F  -5.7854  0.001  -9.0349  -2.536   True
-----------------------------------------------------

ほうほう、それっぽい結果になった。5%水準で、2つの間に差がないという仮説が棄却されるとTrueなので、上のexcelの結果と完全に一致したと言える。1%水準でどうなるのかはどういうオプションを付けるのだろうか?

あと、アプリ化するにはどういう方向で設計しようか。

f:id:k-kuro:20191231235618p:plain
比較する項目数をどう処理しようか考え中。

f:id:k-kuro:20200101163846p:plain
for ループで回すとよいのだろうけど、ちょっと泥臭くif分岐でごまかした。多重比較と言っても12以上も比較することはないだろうからよかろう。

@app.route('/tukey', methods=['GET', 'POST'])
def tukey_index():
    user = g.user.name
    form=StatForm()
    dir = "./user/" + user + "/tukey"
    if os.path.exists(dir):
        shutil.rmtree(dir)
    if not os.path.exists(dir):
        os.makedirs(dir)
    input = dir + "/input.txt"
    output = dir + "/results.txt"

    if request.method == 'POST':
        if 'file' not in request.files:
            flash('No file part')
            return redirect(request.url)
        file = request.files['file']
        if file.filename == '':
            flash('No selected file')
            return redirect(request.url)
        if file and allowed_file(file.filename):
            file.save(input)

        # if form.col1.data:
        #     col1 = form.col1
        # if form.col2.data:
        #     col2 = form.col2
        df = pd.read_csv(input)
        header = df.columns
        record = df.values.tolist()

        # dfs1 = df.iloc[:, 0]
        # dfs2 = df.iloc[:, 1]
        # dfs3 = df.iloc[:, 2]
        data_0 = np.array(df.iloc[:, 0].values.tolist())
        data_1 = np.array(df.iloc[:, 1].values.tolist())
        data_2 = np.array(df.iloc[:, 2].values.tolist())

        # if form.n_samp.data == 3:
        data_arr = np.hstack((data_0,data_1,data_2))
        # ind_arr = np.repeat(list('abc'),len(data_0))
        ind_arr = np.array([])
        ind_arr = np.append(ind_arr, np.repeat(header[0],len(data_0)))
        ind_arr = np.append(ind_arr, np.repeat(header[1],len(data_1)))
        ind_arr = np.append(ind_arr, np.repeat(header[2],len(data_2)))

        count = len(header)-3
        if count > 0:
            count = count -1
            data_3 = np.array(df.iloc[:, 3].values.tolist())
            data_arr = np.hstack((data_0,data_1,data_2,data_3))
            ind_arr = np.append(ind_arr, np.repeat(header[3],len(data_3)))
            if count > 0:
                count = count -1
                data_4 = np.array(df.iloc[:, 4].values.tolist())
                data_arr = np.hstack((data_0,data_1,data_2,data_3,data_4))
                ind_arr = np.append(ind_arr, np.repeat(header[4],len(data_4)))
                if count > 0:
                    count = count -1
                    data_5 = np.array(df.iloc[:, 5].values.tolist())
                    data_arr = np.hstack((data_0,data_1,data_2,data_3,data_4,data_5))
                    ind_arr = np.append(ind_arr, np.repeat(header[5],len(data_5)))
                    if count > 0:
                        count = count -1
                        data_5 = np.array(df.iloc[:, 6].values.tolist())
                        data_arr = np.hstack((data_0,data_1,data_2,data_3,data_4,data_5,data_6))
                        ind_arr = np.append(ind_arr, np.repeat(header[6],len(data_6)))
                        if count > 0:
                            count = count -1
                            data_5 = np.array(df.iloc[:, 7].values.tolist())
                            data_arr = np.hstack((data_0,data_1,data_2,data_3,data_4,data_5,data_6,data_7))
                            ind_arr = np.append(ind_arr, np.repeat(header[7],len(data_7)))
                            if count > 0:
                                count = count -1
                                data_5 = np.array(df.iloc[:, 8].values.tolist())
                                data_arr = np.hstack((data_0,data_1,data_2,data_3,data_4,data_5,data_6,data_7,data_8))
                                ind_arr = np.append(ind_arr, np.repeat(header[8],len(data_8)))
                                if count > 0:
                                    count = count -1
                                    data_5 = np.array(df.iloc[:, 9].values.tolist())
                                    data_arr = np.hstack((data_0,data_1,data_2,data_3,data_4,data_5,data_6,data_7,data_8,data_9))
                                    ind_arr = np.append(ind_arr, np.repeat(header[9],len(data_9)))
                                    if count > 0:
                                        count = count -1
                                        data_5 = np.array(df.iloc[:, 10].values.tolist())
                                        data_arr = np.hstack((data_0,data_1,data_2,data_3,data_4,data_5,data_6,data_7,data_8,data_9,data_10))
                                        ind_arr = np.append(ind_arr, np.repeat(header[10],len(data_10)))
                                        if count > 0:
                                            count = count -1
                                            data_5 = np.array(df.iloc[:, 11].values.tolist())
                                            data_arr = np.hstack((data_0,data_1,data_2,data_3,data_4,data_5,data_6,data_7,data_8,data_9,data_10,data_11))
                                            ind_arr = np.append(ind_arr, np.repeat(header[11],len(data_11)))
                                            
        results = str(pairwise_tukeyhsd(data_arr,ind_arr, alpha=form.alpha.data))
        with open(output, mode='w') as f:
            f.write(results)
        link='Tukeys test success<br><br><a href="/tukey/dl/" target="info2" class="btn btn-default">show results</a>'
        return render_template('/tools/tukey.html', form=form, link=link, header=header, record=record)

    return render_template('/tools/tukey.html', form=form)

@app.route('/tukey/dl/')
def dl_tukey_index():
    user = g.user.name
    dir = "./user/" + user + "/tukey"
    if os.path.exists(dir):
        result = dir +"/results.txt"
        f = open(result, 'r')
        return Response(f.read(), mimetype='text/plain')

統計的検定をpythonで行う〜Student's T test編

統計計算をexcel以外でやる。よく使われるのはRなんだがRはいまいち知らないので、pythonでやれないものかと。まあやれるでしょう。いつものようによく使う機能はまとめてWEBアプリにしてやろう。

f:id:k-kuro:20191231151443p:plain
このグラフデータについてexcelpythonの比較をしてみる。

f:id:k-kuro:20191231151616p:plain
AのJA 20 μMのT検定結果はp value = 0.015736074となっている。

これをpythonでやてみる。

$ python3
Python 3.6.5 (default, Apr 25 2018, 14:26:36) 
[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.39.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy as np
>>> from scipy import stats
>>> a = np.array([12,11,12,10,8])
>>> b = np.array([19,14,19,16,11])
>>> stats.ttest_ind(a,b,equal_var = True)
Ttest_indResult(statistic=-3.0535451414764587, pvalue=0.01573607368058338)

このようにちゃんと一致する。
なお等分散性を仮定(equal_var = True)している。

f:id:k-kuro:20191231172547p:plain
一応アプリにしたけど愛想なさすぎる?

f:id:k-kuro:20191231185818p:plain
一応これでよさげ。

参考ページ
pandasでcsv/tsvファイル読み込み(read_csv, read_table) | note.nkmk.me

PCA plotをpythonで行うWEBアプリ(ver2)

さて、さっきのアプリからPCAだけを抜き出してちょっと改変してみる。

主成分分析を Python で理解する - Qiita
こちらのページを参考にさせてもらう。

ポイントはnumpyで数値データを抜き出していた点をpandasに換えることでテキストも含んだcsvを入り口にできるようにした点とクラスわけを色で塗り分けるようにした点。
おまけでfigure sizeを設定できるようにもしてみた。

f:id:k-kuro:20191230223252p:plain
PCA by python ver2

前に作ったstat.pyに以下の記述を追加した。

@app.route('/stat2', methods=['GET', 'POST'])
def stat2_index():
    user = g.user.name
    form=StatForm()
    dir = "./user/" + user + "/stat"
    if os.path.exists(dir):
        shutil.rmtree(dir)
    if not os.path.exists(dir):
        os.makedirs(dir)
    input = dir + "/input.txt"
    output = dir + "/output.txt"


    if request.method == 'POST':
        if 'file' not in request.files:
            flash('No file part')
            return redirect(request.url)
        file = request.files['file']
        if file.filename == '':
            flash('No selected file')
            return redirect(request.url)
        if file and allowed_file(file.filename):
            file.save(input)

        stat_fig = dir + "/stat_" + str(time.time()) + ".png"
        if form.fig_x.data:
        	fig_x=form.fig_x.data
        if form.fig_y.data:
        	fig_y=form.fig_y.data

        df = pd.read_csv(input, sep=",", index_col=0)
        dfs = df.iloc[:, 1:].apply(lambda x: (x-x.mean())/x.std(), axis=0)
        pca = PCA()
        feature = pca.fit(dfs)
        feature = pca.transform(dfs)
        plt.figure(figsize=(fig_x,fig_y))
        plt.scatter(feature[:, 0], feature[:, 1], alpha=0.8, c=list(df.iloc[:, 0]))
        plt.grid()
        plt.title('principal component')
        plt.xlabel("PC1")
        plt.ylabel("PC2")
        plt.savefig(stat_fig)
        img_url = "../../static/" + stat_fig
        return render_template('/tools/stat2.html', form=form, img_url=img_url)

    return render_template('/tools/stat2.html', form=form)


templateの方は殆ど変わらないが
stat2.html

{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}

{% block title %}Statistics_tool{% endblock %}

{% block head %}
{{ super() }}


{% endblock %}

{% block scripts %}
{{ super() }}
{% endblock %}

{% block page_top %}
<h2>PCA tool</h2>
<div class="row">
  <form class="form form-group form-group-sm" method=post enctype=multipart/form-data>
  <div class="col-xs-2">
    <h4>Figure setting</h4>
    <h5>
    figure_x size  <br>
    {{ form.fig_x }}<br><br>
    figure_y size <br>
    {{ form.fig_y }}<br><br>
      </h5>
  </div>

  <div class="col-xs-10">

    <br>
      <b>Upload csv file</b><br>
      <input type=file name=file>
<br>
      <b>start statistics</b><br>

      <input class='btn btn-primary' type=submit value=Submit>

    {% if img_url %}
    <p><img src="{{ img_url }}"></p>
      {% endif %}
      <br><br>

    {% for message in get_flashed_messages() %}
        <div class="alert alert-success">{{ message }}</div>
        {% endfor %}
    <br><br>
    <div>
    <iframe name="info2" frameborder="0" width=1220px height=600px></iframe>
    </div>
  </div>
    </form>
</div>

{% endblock %}

{% block left %}
{% endblock %}

{% block right %}
{% endblock %}

{% block page_bottom %}

{% endblock %}


f:id:k-kuro:20191230225808p:plain

要素の側のプロットもつけられるようにした。これでほぼ完成。

PCAやMDS plotをpythonで行うWEBアプリ(ver1)

以前にもpythonでPCAを実施するスクリプトを書いてみていたが、webアプリ版を作ってみた。まずは低機能にとりあえず数値だけのcsvファイルを投げるとプロットを描かせるだけのものから

f:id:k-kuro:20191230183032p:plain
PCA by Python

こんな感じ。

Flaskのviewsはこんなふうで。
/flask_root_folder/app/views/stat.py

# PCA, MDS, tSNEのプロットを作成する
from functools import wraps
from flask import request, redirect, url_for, render_template, flash, Blueprint, session, g, send_file, current_app, Response
from flask_wtf import FlaskForm
from wtforms import SubmitField, SelectField
from werkzeug.utils import secure_filename
import csv, os, shutil, subprocess, sys, re, time
import numpy as np
from matplotlib import pyplot as plt
from sklearn.decomposition import PCA
from sklearn.manifold import MDS, TSNE
import matplotlib
import matplotlib.pyplot as plt

app = Blueprint('stat', __name__)
ALLOWED_EXTENSIONS = set(['txt', 'csv'])   #選択できる入力ファイルの拡張子
def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

class StatForm(FlaskForm):
    stat_type = SelectField('statistics type', choices=[('PCA', 'PCA'), ('MDS', 'MDS'), ('tSNE', 'tSNE')])
    submit = SubmitField('Submit')

@app.route('/stat', methods=['GET', 'POST'])
def stat_index():
    user = g.user.name
    form=StatForm()
    dir = "./user/" + user + "/stat"
    if os.path.exists(dir):
        shutil.rmtree(dir)
    if not os.path.exists(dir):
        os.makedirs(dir)
    input = dir + "/input.txt"
    output = dir + "/output.txt"


    if request.method == 'POST':
        if 'file' not in request.files:
            flash('No file part')
            return redirect(request.url)
        file = request.files['file']
        if file.filename == '':
            flash('No selected file')
            return redirect(request.url)
        if file and allowed_file(file.filename):
            file.save(input)
        npArray = np.loadtxt(input, delimiter = ",")
        X = np.array(npArray)
        stat_fig = dir + "/stat_" + str(time.time()) + ".png"

        if form.stat_type.data == "PCA":

            pca = PCA()
            pca.fit(X)
            transformed = pca.fit_transform(X)
            plt.figure()
            plt.scatter(transformed[:, 0], transformed[:, 1])
            plt.title('principal component')
            plt.xlabel('pc1')
            plt.ylabel('pc2')
            stat_result = pca.explained_variance_ratio_
            plt.savefig(stat_fig)
            img_url = "../../static/" + stat_fig # flask_root_folder/app/staticにflask_root_folder/userのリンクを張ってある

            return render_template('/tools/stat.html', stat_result=stat_result, form=form, img_url=img_url)

        if form.stat_type.data == "MDS":

            mds = MDS(n_jobs=4)
            mds.fit(X)
            transformed = mds.fit_transform(X)
            plt.figure()
            plt.scatter(transformed[:, 0], transformed[:, 1])
            plt.title('Multidimensional scaling')
            plt.savefig(stat_fig)
            img_url = "../../static/" + stat_fig

            return render_template('/tools/stat.html', form=form, img_url=img_url)

        if form.stat_type.data == "tSNE":

            tsne = TSNE()
            tsne.fit(X)
            transformed = tsne.fit_transform(X)
            plt.figure()
            plt.scatter(transformed[:, 0], transformed[:, 1])
            plt.title('t-distributed Stochastic Neighbor embedding')
            plt.savefig(stat_fig)
            img_url = "../../static/" + stat_fig

            return render_template('/tools/stat.html', form=form, img_url=img_url)

    return render_template('/tools/stat.html', form=form)

template
flask_root_folder/app/templates/tools/stat.html

{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}

{% block title %}Statistics_tool{% endblock %}

{% block head %}
{{ super() }}


{% endblock %}

{% block scripts %}
{{ super() }}
{% endblock %}

{% block page_top %}
<h2>Statistics tool</h2>
<div class="row">
  <form class="form form-group form-group-sm" method=post enctype=multipart/form-data>
  <div class="col-xs-1">
  </div>

  <div class="col-xs-10">

    <br>
      <b>Upload csv file</b><br>
      <input type=file name=file>
<br>
      <b>statistics type</b><br>
      {{ form.stat_type }}<br><br>
      <b>start statistics</b><br>

      <input class='btn btn-primary' type=submit value=Submit>



    {% if img_url %}
    <p><img src="{{ img_url }}"></p>
      {% endif %}
      <br><br>
      {{ stat_result|safe }}<br>
    {% for message in get_flashed_messages() %}
        <div class="alert alert-success">{{ message }}</div>
        {% endfor %}
    <br><br>

  </div>
    </form>
</div>
<div class="col-xs-1">
</div>
{% endblock %}

{% block left %}
{% endblock %}

{% block right %}
{% endblock %}

{% block page_bottom %}

{% endblock %}

firewall-cmdのお作法

これまたしょっちゅう・・・

activeなゾーンの表示

# firewall-cmd --list-all
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: eth0
  sources: 
  services: dhcpv6-client ssh
  ports: 
  protocols: 
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 

似ているけど

# firewall-cmd --list-all-zones
block
  target: %%REJECT%%
  icmp-block-inversion: no
  interfaces: 
  sources: 
  services: 
  ports: 
  protocols: 
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 
	

dmz
  target: default
  icmp-block-inversion: no
  interfaces: 
  sources: 
  services: ssh
  ports: 
  protocols: 
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 

・・・・・・

work
  target: default
  icmp-block-inversion: no
  interfaces: 
  sources: 
  services: dhcpv6-client ssh
  ports: 
  protocols: 
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 

こんな感じにずらずらと一覧。

get系

# firewall-cmd --get-active-zones
public
  interfaces: eth0

# firewall-cmd --get-default-zone
public

# firewall-cmd --get-services
RH-Satellite-6 amanda-client amanda-k5-client amqp amqps apcupsd audit bacula bacula-client bgp bitcoin bitcoin-rpc bitcoin-testnet bitcoin-testnet-rpc ceph ceph-mon cfengine condor-collector ctdb dhcp dhcpv6 dhcpv6-client distcc dns docker-registry docker-swarm dropbox-lansync elasticsearch etcd-client etcd-server finger freeipa-ldap freeipa-ldaps freeipa-replication freeipa-trust ftp ganglia-client ganglia-master git gre high-availability http https imap imaps ipp ipp-client ipsec irc ircs iscsi-target isns jenkins kadmin kerberos kibana klogin kpasswd kprop kshell ldap ldaps libvirt libvirt-tls lightning-network llmnr managesieve matrix mdns minidlna mongodb mosh mountd mqtt mqtt-tls ms-wbt mssql murmur mysql nfs nfs3 nmea-0183 nrpe ntp nut openvpn ovirt-imageio ovirt-storageconsole ovirt-vmconsole plex pmcd pmproxy pmwebapi pmwebapis pop3 pop3s postgresql privoxy proxy-dhcp ptp pulseaudio puppetmaster quassel radius redis rpc-bind rsh rsyncd rtsp salt-master samba samba-client samba-dc sane sip sips slp smtp smtp-submission smtps snmp snmptrap spideroak-lansync squid ssh steam-streaming svdrp svn syncthing syncthing-gui synergy syslog syslog-tls telnet tftp tftp-client tinc tor-socks transmission-client upnp-client vdsm vnc-server wbem-http wbem-https wsman wsmans xdmcp xmpp-bosh xmpp-client xmpp-local xmpp-server zabbix-agent zabbix-server

serviceは
/usr/lib/firewalld/services/
xmlファイルとして定義されているもの

# firewall-cmd --add-service=https --zone=public --permanent
# firewall-cmd --reload
success

というふうに開通したいサービスをゾーンに登録できる
permanentオプションをつけると起動時に自動で開き、つけないとその時限りとなる。permanentオプションを付けるときはreloadオプションも必要。

# firewall-cmd --add-port=8080/tcp --zone=public

ポートを直接指定して開く場合はこのようにする。通常のサービスとは異なるポートを使いたいときなど。

nmcliのお作法

すぐに忘れてしょっちゅう調べている気がするので、覚書。

基本のネットワーク設定

# nmcli c m eth0 ipv4.method manual ipv4.addresses 192.168.1.101/24 ipv4.gateway 192.168.1.1 ipv4.dns 8.8.8.8 connection.autoconnect yes

この例ではDHCPをやめて手動設定にする、ipアドレスゲートウェイアドレス、DNSサーバ、そして起動時に自動でネットワークデバイスを有効にする設定を一気に行っているが、必要な部分だけを書いてもいい。
設定変更したあとは再起動するか、

# nmcli c down eth0; nmcli c up eth0

でネットワークをリセットする。

現在の状況を表示

# nmcli d
DEVICE  TYPE      STATE      CONNECTION 
eth0    ethernet  connected  eth0       
lo      loopback  unmanaged  --         

firewalldで使うゾーンの設定も行える。

# nmcli c m eth0 connection.zone internal

なお、nmcli c のcはconnectionの省略でcと書く代わりにconnectionと書いてもいい。

  g[eneral]       NetworkManager's general status and operations
  n[etworking]    overall networking control
  r[adio]         NetworkManager radio switches
  c[onnection]    NetworkManager's connections
  d[evice]        devices managed by NetworkManager
  a[gent]         NetworkManager secret agent or polkit agent
  m[onitor]       monitor NetworkManager changes

またnmcli c mのmはmodifyの省略形でmodやmodifyでもいいようだ。

CentOS7に仮想環境(続き)

もともとKVMホストeth1にあてがっていた192.168.0.11をブリッジにあてがう。

# nmcli c a type bridge ifname br0
# nmcli c m bridge-br0 bridge.stp no
# nmcli c m bridge-br0 ipv4.method manual ipv4.address "192.168.0.11/24" ipv4.gateway "192.168.0.1" ipv4.dns 8.8.8.8
# nmcli c a type bridge-slave ifname eth1 master bridge-br0
# nmcli c delete eth1

ここまではいいはず。
この状態でsshで192.168.0.11にアクセスするとKVMホストにログインできる。

仮想マシン

# virt-install --name kvm2 --memory 1024 --disk size=20 --vcpu 4 --location /tmp/CentOS-7-x86_64-DVD-1908.iso --network bridge=br0 --graphics none --extra-args='console=tty0 console=ttyS0'

で作成する。
インストールが始まったらNATを使う場合に空欄にしておいたnetworkの設定項目も入力する。
DHCPも使えるが、わざわざブリッジにするならやはりここはstaticアドレスを入れておきたい。
ネット空間はホストと同じ192.168.0.xxでOKだ。

これにて無事、仮想マシンに直接sshでログインできるようになった。
さて、ちゃんとできるになったことだしメインのサーバにも仮想マシンを立てて、通常の作業はそちらで行うのが良いだろうな。

# virt-clone --original kvm1 --name centos7template --file /home/kvm/images/centos7template.img

インストールした仮想環境をコピーしてテンプレートにしておけば環境をいくらでも増やしていける。disk imgはバックアップとしてすげ替えることも可能。

ここからは既存の仮想環境のネットワーク設定を変える試み。しかしうまくいっていない。普通に仮想環境を作り直したほうが早そうだ。

# nano host-bridge.xml
# cat host-bridge.xml
<network>
  <name>host-bridge</name>
  <forward mode="bridge" />
  <bridge name="br0"/>
</network>

# virsh net-define --file host-bridge.xml
# virsh net-autostart host-bridge

# virsh edit kvm1
<source network='default'/>

<source network='host-bridge'/>

に書き換える。

CentOS7に仮想環境を構築

だいぶ仕事がまとまってきて、新しく学生なども入ってくるようになった。こうなるとそのうちインフォマティクスをやりたいという学生もきっと入ってくるだろう。最初のうちはとりあえず自分のPCでどうにかしてもらえばいいが、そのうちきっとサーバを触ることになるに違いない。
自分の経験からして、最初の頃はメモリオーバーフローだとかでしょっちゅうハングアップさせてたから、やはり最初は実機ではなく仮想環境だけでやってもらうのが得策だろう。
ということで、休日を利用して仮想環境構築を試してみるのであった。

まずは昨日RAIDをキャンセルしてCentOS7をインストールしたRX200S6 (E5630*2/4G/100G)を用意する。ネットワーク上はメインサーバの内側のサブLANに位置しているが、クラスタには組み込んでおらず、NISによるユーザー共有もあえて行っていない。とりあえずはできるだけシンプルな構成で試してみたいからね。

# yum install -y qemu-kvm libvirt virt-install libvirt-python libvirt-client

んでサクッと起動

# systemctl start libvirtd
# systemctl enable libvirtd

NASにおいてあったCentOS-7-x86_64-DVD-1908.isoを/tmp/にコピーしておいて

# virt-install --name kvm1 --memory 1024 --disk size=20 --vcpu 4 --location /tmp/CentOS-7-x86_64-DVD-1908.iso --network default --graphics none --extra-args='console=tty0 console=ttyS0'

実機のメモリ・HDDスペックにあまり余裕が無いのでミニマムなサイズで試す。

インストールの開始中...
ファイル .treeinfo を読み出し中...                      |  354 B  00:00     
ファイル vmlinuz を読み出し中...                        | 6.4 MB  00:00     
ファイル initrd.img を読み出し中...                     |  53 MB  00:00     
割り当て中 'kvm1.qcow2'                                |  20 GB  00:05     
ドメイン kvm1 に接続しました
エスケープ文字は ^] です
[    0.000000] Initializing cgroup subsys cpuset
[    0.000000] Initializing cgroup subsys cpu
[    0.000000] Initializing cgroup subsys cpuacct
[    0.000000] Linux version 3.10.0-1062.el7.x86_64 (mockbuild@kbuilder.bsys.centos.org) (gcc version 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC) ) #1 SMP Wed Aug 7 18:08:02 UTC 2019
[    0.000000] Command line: console=tty0 console=ttyS0

・・・・・・・

Starting installer, one moment...
anaconda 21.48.22.156-1 for CentOS 7 started.
 * installation log files are stored in /tmp during the installation
 * shell is available on TTY2
 * when reporting a bug add logs from /tmp as separate text/plain attachments
02:32:38 Not asking for VNC because we don't have a network
================================================================================
================================================================================
Installation

 1) [x] Language settings                 2) [!] Time settings
        (English (United States))                (Timezone is not set.)
 3) [!] Installation source               4) [!] Software selection
        (Processing...)                          (Processing...)
 5) [!] Installation Destination          6) [x] Kdump
        (No disks selected)                      (Kdump is enabled)
 7) [ ] Network configuration             8) [!] Root password
        (Not connected)                          (Password is not set.)
 9) [!] User creation
        (No user will be created)
  Please make your choice from above ['q' to quit | 'b' to begin installation |
  'r' to refresh]:

sshでアクセスしてインストールしているのでGUIは開かずこんな感じでcuiインストーラが立ち上がる。
まあ設定内容はGUIのインストールをやったことがあればだいたい想像できるはず。
とりあえずネットワークだけ何も設定せずにインストールしてみる。

Performing post-installation setup tasks
.

Configuring installed system
.
Writing network configuration
.
Creating users
.
Configuring addons
.
Generating initramfs
.
Running post-installation scripts
.
        Use of this product is subject to the license agreement found at /usr/share/centos-release/EULA

        Installation complete.  Press return to quit

こんな感じでインストール完了。

returnを押すとずらずらと見慣れた起動シークエンスが流れ、

CentOS Linux 7 (Core)
Kernel 3.10.0-1062.el7.x86_64 on an x86_64

localhost login: 

と無事に起動した。

なお、インストール〜再起動するとKVM内でシステムが立ち上がるが、起動していない状態から起動するにはコンソールから

# virsh list --all
 Id    名前                         状態
----------------------------------------------------
 4     kvm1                           実行中

# virsh start kvm1

とする
シャットダウンは普通に仮想環境内で
sudo shutdown -h now
すればいいが、
仮想環境外から

# virsh shutdown kvm1

で強制終了もできるようだ。

なお起動しても裏で起動しているだけでコンソールに入れないので

# virsh console kvm1

としてコンソールに入る。

この状態ではネットワークにはつながっていないので、次はネットワークの設定だな。
まずはお手軽なIPマスカレードによるNAT方式を試す。
この場合一旦ホストにログインしないと仮想環境に外から直接アクセスできないのでちょっと不便。ただしその分セキュリティーも高くできるかな。
現在の場合ホストがサブLANに接続しているし、完全に外部からアクセスしようとすると外→VPN→学内LAN→Lab内LAN→クラスタ用サブLAN→KVMホスト→仮想環境という感じでめちゃめちゃ奥底にあることになる。最終的にはLab内LANから直接アクセスできるくらいにはしたい。

# virsh attach-interface kvm1 network default

以上。
これだけで仮想環境kvm1から外のネットワークまで出ることができる様になった。pingyum updateもOKなはず。

設定を変更すべくネットワークを遮断するなら

virsh detach-interface kvm1 --type network --mac XX:XX:XX:XX:XX:XX

MACアドレスはkvm1のなかでip aで調べる。仮想MACなのでattach-interfaceするたびにランダムに変更されるため注意。


次にブリッジ接続をためす。
これだと物理NICに仮想アドレスを接続するのでホストに一旦ログインしなくてもsshで入れるはず。

とおもって早速ちょっと設定をし始めたらすぐにホストごとハングして、にっちもさっちもいかなくなった。

# brctl addbr br0
# brctl addif br0 ens1f0

こうするとだめだった。

やっぱブリッジは難しい。少なくとも実機のそばでやらないと、遠隔地からネットワーク越しにやってると、ネットワークの設定をミスった時点で何もできなくなる。というわけで休日の遠隔アクセス終了っと。