日々の備忘録

技術について日々学んだことを書きます。

linux screen での画面分割 & 対話シェル起動 & コマンド実行をscreenrcで管理する方法

ターミナルアプリ自体を複数起動するのもアレなので、screenで画面分割しているんですが、最近まで窓をいちいち手動で作成していました。

だいたい窓を4つ、それぞれ打つコマンドも同じなので定型処理としてなんとかできないかと思っていたんですが、screenrcに記載できるんですね。

# シェル起動
screen 0
screen 1 command args...

# 水平、垂直分割
split
split -v

# 対象としている窓を移動
focus (up|down|left|right)

# 起動しているシェルを現在の窓に割り当て
select shell_number

ざっくりとですが、screenrcに以上のように記載し、screen実行時に -c オプションで読み込ませると記載した通りになります。

そこで、screen command 形式でいつもどおりに実施しているコマンドをつらつらと記載してみたのですが、複数コマンドを && で繋いだり、リダイレクトしたりしようとすると上手くいきません。

どうやら、commandを省略すると対話シェルが起動しますが、commandを記載すると対話シェルが起動しないので複数コマンドを解決できないようです。

以下のように記載すれば実行自体はできるんですが、プロセスが終了したり、Ctrl+Cなどで中止するとシェルが残らない?ので完全な定型処理でない場合は若干不便です。

screen bash -c "echo \"foo\" | grep \"f\""

zombieを指定すると、コマンドの再実行はできますが、やはり対話シェルを起動できません。

そこで、bashのプロセス置換で、--init-file と組み合わせてワンライナーで起動するようにしました。

screen bash -c "bash --init-file <(echo \"source \"$HOME/.bashrc\"; pwd;\")"

リダイレクトしているのでbash -cで実行するようにしないと上手くいきません。

ここまでやるなら、素直にコマンド部分をスクリプトファイルにして、screen bash --init-file ./script.sh としたほうが保守性の面でもいいかもしれませんがscreenrc単体で一応実現できました、という話でした。

screenrcに記載できること自体、最近知ったばかりで浅い知識なのでもっとスマートに実現できるよという方がいらっしゃったら教えていただければ幸いです。

Systemdでマウントするタイミングで開始するサービスを作る

あまり需要はないと思いますが、解決できたので書いてみます。

Vagrantを使って、synced_folder(vboxsf)でホストOS(Windows)側のソースコードを反映させて... という流れで開発を行っていました。

ゲストOSにはCentos7を入れています。

本番環境でSystemdを使うので、開発環境でも同様に自作サービスを登録してBoot時に自動起動するようにしていたんですが、synced_folderでマウントするタイミングと、自作サービスが開始するタイミングが若干前後し、サービスは起動しているが、コードがないのでエラーを吐くという残念なことが度々起きていました。

Vagrantのprovisionで該当サービスをrestartもしくはreloadさせるか、そもそもBoot時に起動させずに、同じくprovisionでstartさせるかすれば解決するんですが、Systemdに習熟する意味もあり、いろいろ調べた次第です。


Systemdには他サービスとの依存関係を明示する方法があります。

例えば、syslogの準備が完了してから起動させたい場合は以下のように記載します。

[Unit]
...
After=syslog.target

このようにしておくと、自動起動時にsyslogの後に起動するようになります。

syslogの起動に失敗したら開始しなくてもいいのであれば、以下の一行をUnitセクションに追加します。

Requires=syslog.target

また、Systemdではマウント情報も取得できます。

通常のサービスはfoo.serviceといったものになりますが、マウントはbar.mountといった形になります。

ただ、After=bar.mountとしても上手くいきません。

systemd-analyze plot > foo.svg としてsvgファイルをWebブラウザで開けばわかるかと思いますが、synced_folderによるマウントは起動中にVagrantが実施しているので自動起動時の依存関係に組み込まれることはありません。

なので、搦め手になりますが、InstallセクションのWantsByを使います。

Installセクションは、systemctl enableを実行する際にどうするのかを明示するセクションです。

例えば、以下のようにするとrunlevel 3で起動するサービスになります。

[Install]
WantsBy=multi-user.target

これをmountと組み合わせて以下のようにします。

[Install]
WantsBy=vagrant.mount

あとは変更を反映するために、systemctl daemon-reload して、systemctl disable && systemctl enable でリンクを張り直します。

こうすることで、/vagrantに共有フォルダがマウントされるタイミングでサービスが起動されます。

注意点として、Installセクションは必ずserviceファイル本体に記載するようにしてください。

Systemdでは、foo.service.d/baz.confとして、Drop-in形式で一部分の追記、上書きが可能ですが、Installセクションに関してはDrop-in形式で記載しても全く効果はありません。


以上で、マウント時に起動するサービスを作ることができました。

冒頭でも記載しましたが、開発環境の問題で他に単純な解決策はあるのでこれを無理に使う必要はありません。

Systemdにはいろいろな機能があるようなので気になる方は調べてみるといいかもしれません。

Visual Studio Code で Git Clone

今更ながら、Visual Studio Codeを使い始めたのですが、手始めにGit Cloneをどうすればいいのかわからなかったので公式ドキュメントを漁ってみました。

環境

Git binにはPathを通しておきます。

VS2017や、Eclipseに慣れているせいもあり、ついプロジェクト作成メニューを探してしまいましたが、そういったものがなく焦りました...

How do I create and run a new-project に記載されているとおり、プロジェクト作成機能はそもそもないようです。

話は戻りまして、そのものずばり、Cloning a repository に書いてあったのですが、メモとして残しておきます。

VS Codeには Command Palette なる機能がついているようで、Ctrl + Shift + P で入力欄が表示されます。

ここでいろいろとできるようですが、ひとまず Git と入力すると候補が出てくるので、 Git: Clone を選択し、リポジトリURLを入力するとどこにCloneするかを聞かれるので、あとは流れで。

今回はGithubのPublic Repositoryだったので、Private Repository や git over ssh の場合はどうするのか試せていません。

Clone以外にも、ローカルリポジトリ作成などGit Commandだけでもかなり対応されているようなのでどんどん使っていきたいと思います。(gitに慣れ親しんでいるのであれば、TERMINALビューからgitコマンドを直接叩いたほうが早そうですが)

Ansible taskを1ノードずつ実行する3つの方法

Ansible において、特段工夫をせずにtaskを記述していると、全ノードに対して一度に処理を実行することになります。

サーバの再起動やサーバミドルウェアの更新などが必要になる場合、仮に冗長構成を取っていたとしてもダウンタイムが発生してしまう可能性があります。

メンテナンス期間を設けて、その間は利用できないことにしてもいいのですが、止めることができない場合は一度に1ノードずつ処理を実行する、いわゆるローリングアップデートが必要になります。

Ansibleでローリングアップデートを実現するにはいくつか方法があります。

確認した環境

方法1. serial

以下の公式リンクで記載されている通り、Ansibleではserialキーワードがサポートされています。

Delegation, Rolling Updates, and Local Actions — Ansible Documentation

以下のようなplaybookを記載すると、hosts: allに対して1ノードずつ xxx サービスを再起動してくれます。

---
- name: rolling update (serial)
  hosts: all
  serial: 1
  tasks: 
    - name: service restart 
      service: 
        state: restarted
        name: xxx

ただし、serialキーワードはtask単位で使用することができないため、特定taskだけローリングアップデートしたいといった用途には向きません。(サポートされればいいんですが...)

方法2. delegate_to + run_once

run_onceを使うと、実行グループの先頭ホストでのみtaskが実行されます。 これとdelegate_toを組み合わせて、各ノードに処理を委譲させることで、ローリングアップデートを実現することができます。

---
- name: rolling update (run_once)
  hosts: all
  tasks: 
    - name: service restart
      service: 
        state: restarted
        name: xxx
      delegate_to: "{{ item }}"
      run_once: yes
      with_items: "{{ play_hosts }}"

この方法で大抵は解決すると思いますが、やはり欠点があります。

serialは一度に実行する数を指定することができますが、こちらは必ず1ノードずつ実行されます。

また、上述の通り、run_onceの仕様として実行時のグループの先頭ホストが主体となって処理が行われます。 そのため、whenなどで先頭ホストで実行されないtaskとなってしまうと、taskの各ノードへの委譲自体がされなくなるため、結果的に全ノードでskipされてしまうことになります。

方法3. include_tasks

私自身run_onceを使っていたのですが、その仕様によってtaskが実行されない状況に陥ってしまいました。

何とか解決できないかと調べていたところ、以下のgithub issuesのコメントにて別の方法が提示されていました。

support for "serial" on an individual task · Issue #12170 · ansible/ansible · GitHub

include_tasksとloopを組み合わせて動的にtaskを生成する方法です。

# playbook.yml
- name: rolling update (include_tasks)
  hosts: all
  tasks: 
    - name: service restart
      include_tasks: include.yml
      with_items: "{{ play_hosts }}"
      when: inventory_hostname == host_item
      loop_control: 
        loop_var: host_item

# include.yml
- name: service restart
  service: 
    state: restarted
    name: xxx

こうすることで、ローリングアップデートが可能になります。

各ノードで実行するtaskをそれぞれ動的に作成しているため、結果的に1ノードずつ実行されるtaskが出来上がります。

イメージとしては以下のymlに相当します。

- tasks: 
  - name: service restart host1
    service: 
      state: restarted
      name: xxx
    when: inventory_hostname == host1

  - name: service restart host2
    service: 
      state: restarted
      name: xxx
    when: inventory_hostname == host2

注意点として、include_tasksはtagsの影響を受けます。 tags指定で実行すると、includeされる側のymlが読まれずにバグを引き起こすことになります。 なのでincludeされる側のymlは以下のようにしておくといいです。

- name: service restart
  service: 
    state: restarted
    name: xxx
  tags: 
    - always

これでたとえtagsでフィルタされたとしても必ず実行されることになります。

まとめ

以上、3つの方法を紹介しました。

一口にAnsible でローリングアップデートと言っても複数の方法があります。

それぞれの特性を把握した上で最も適している方法を選ぶといいと思います。

ブログを開設しました

はじめまして!airhandと申します。

仕事では主にWebプログラムを担当、インフラも少しだけ携わっています。

今までネットではいろんな方から知識をいただくばかりで、アウトプットを行っていなかったので今年こそは…ということでブログを開設してみました。

技術的なことを書いていこうと思うので、拙いかと思いますが宜しくお願いします( `・∀・´)ノヨロシク