それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

Goole App Script ( GAS ) の基本的な操作方法。11. ボタンに登録して実行

前回まで

前回までで、コードをスッキリさせて、好きなタイミング実行できるようにした。

今回は、ボタンで実行

今回は、さらにわかりやすくするために、ボタンに登録して実行することにする。

ボタンで実行を作る。

ボタンそのものは存在しないので、図形オブジェクトとして追加する。

手順

以下の手順で作成します。

  • オブジェクトを追加(図形描画)
  • ボタンを作成
  • ボタンをのメニューをクリック
  • スクリプトの割当

オブジェクトの追加

図形描画で図形を作ります。

f:id:takuya_1st:20210604044223p:plain

ボタンを図形描画で作る。

同じ図形を2つ重ねて、枠線の太さと色を変えてボタンっぽくします。 f:id:takuya_1st:20210604044737p:plain

出来ました。

ボタンっぽい画像を作りました。

f:id:takuya_1st:20210604044230p:plain

クリックしてメニューを表示

メニューは、図形を選択すると現れます。

f:id:takuya_1st:20210604044236p:plain

スクリプトの割当。

関数名を指定します。

f:id:takuya_1st:20210604044242p:plain

図形をクリック

図形をクリックすると、割り当てた関数名が実行されます。

f:id:takuya_1st:20210604044710p:plain

スクリプト割当後の注意。

いちど割り当てると、クリックで起動する。

マウスクリックが不能になる。移動・変更は右クリックでやる。

続く

長いので分割しました。

次回に続く→12

 

 

 

Goole App Script ( GAS ) の基本的な操作方法。10.メニューに登録して実行

前回の続き

前回までで、一通り実行ができるようになったので、他人に使ってもらうためにメニューに追加して実行する方法を見ておく。

今回はメニュー

今回は、メニューから好きなタイミングで任意に、スクリプトを実行します。

メニューで実行

メニューに登録して、任意のタイミングで実行。

function addCustomMenu(){
 //メニューを追加
  let my_menu = new Array();
  my_menu.push({name:"ダミー", functionName: "menu_dummy"});
  let s = SpreadsheetApp.getActiveSpreadsheet().addMenu("!!サンプル", my_menu);
}

メニューになる。

この関数を実行すると、メニューが生成され、いつでもスクリプトを実行できる。

f:id:takuya_1st:20210604043437p:plain

起動後にメニューを生成

ドキュメントを開いたあとに、メニューを自動生成することもできる。

onOpen関数を定義すると、ドキュメントを開いた瞬間に実行される。onOpenは存在するだけで自動実行される。

function onOpen(){
    addCustomMenu();
}

メニューを追加(別の方法)

先の方法は、ショートカット的な方法で、メニューを作るのは、こっちがGAS的に正統方法だと思う。

function onOpen(){
  SpreadsheetApp.getUi().createMenu("!サンプル")
  .addItem('アイテム1', "func_name")
  .addItem('アイテム2', "func_name")
  .addSeparator()
  .addItem('アイテム3', "func_name")
  .addItem('アイテム4', "func_name")
  .addItem('アイテム5', "func_name")
  .addSeparator()
  .addSubMenu(
    SpreadsheetApp.getUi()
    .createMenu("アイテム6")
    .addItem('アイテム6-1', "func_name")
    .addItem('アイテム6-2', "func_name")
    .addItem('アイテム6-3', "func_name")
  )
  .addSeparator()
  .addItem('アイテム7', "func_name")
  .addToUi()

}

メニューの上書き

同じ名前で登録すると上書きされるので注意。

続く

長いので分割しました。

次回に続く→11

Goole App Script ( GAS ) の基本的な操作方法。09. 起動時に実行

前回まで

前回までで、ソースの管理、クラス、デバッグを見てきた。

そろそろ十分に準備が整ってきた。

今回はスクリプトの実行タイミング

ファイルを開いたとに、スクリプトを起動する方法を見る。

ドキュメントが開いたときにスクリプト起動

ドキュメントが開いときにスクリプトを起動する onOpen

onOpen 関数を定義しておけば、ファイルを開いときに自動的に実行してくれる。

f:id:takuya_1st:20210604040942p:plain

ドキュメントを開いたとき実行は、少し遅い

onOpenの実行タイミングは、思った以上に遅いので注意する。

一見すると、ファイルが開いてるように見えてもすべての準備が整うまで実行されない。

ドキュメントのローディングバーが進む

メニューの下のローディングバーが進んで消える。

f:id:takuya_1st:20210604041213p:plain

そのあと、最終更新時刻などが取得される。

f:id:takuya_1st:20210604041345p:plain

その後に、ファイルがロードされてonOpenが実行される。(数秒は掛かる。)

f:id:takuya_1st:20210604041137p:plain

シートが複雑だと、そこそこ時間がかかるので辛抱強く待つ必要がある。

複数ファイルにまたがった場合の onOpen

opOpen関数は、どこに書いてもいい。

スクリプトを複数のファイルに分割して書いたとしても、全部ロードしてから onOpenが探される。そのためonOpenをうっかり複数書くと後からロードされたものが優先される。

onOpen 関数はシンプルイベントハンドラなので、いつでも使える。

その他の実行方法

スクリプト・エディタで実行できるのだが、ドキュメント/スプレッド・シートからスクリプトを実行されないと使いにくい。

ファイルが開いたときに実行される onOpen があり、その他にもボタン(図形オブジェクト)にイベントハンドラを貼り付けて使うことができる。

続く

長いので分割しました。

次回に続く→10

  

  

Goole App Script ( GAS ) の基本的な操作方法。08.デバッガとグローバル・オブジェクト

前回まで

前回までで、スクリプトを作って、ファイルに分割してクラスに分けて管理するまで見た。

そろそろまともなコーディングができそうな気がする

今回は

今回はデバッグを使う。デバッグツールを使いGlobalオブジェクトを確認する。

デバッグで実行

行番号をクリックしてブレークポイントを設置できる。

f:id:takuya_1st:20210604033900p:plain

ブレークポイントで止めておけば、グローバルオブジェクトをすべて参照できるので便利。

グローバルオブジェクトの一覧

デバッガを起動しブレークポイントで止めてけば、グローバルオブジェクトの一覧を見ることができる。

global な object 一覧は一度見ておくと、スッキリする。

意外なオブジェクトがあったりする。あと名前のルールが分かっていいと思う。

f:id:takuya_1st:20210604034006p:plain

Utilities クラスとかあったりする。

続く

長いので分割しました。

次回に続く→09

Goole App Script ( GAS ) の基本的な操作方法。07.クラス作成でコード管理。

前回の続き

前回までで、スクリプトを実行し、関数に分割して管理、ファイルに分割して管理、ネットアクセスまでみた。

今回はクラス

今回は、ファイルに分割してするだけじゃなく、クラスにまとめるほう法をみて、さらにコードをスッキリさせる方法を見ておく。

classを使う。

class も完全な互換性があるわけでないが、ある程度使える。

ちょっと古い書式のJavaScriptのclassなので、たまにイライラするかもしれない。

クラスのサンプル

次のようなクラスをGASに作って実行してみます。

class MyClass{

  constructor() {
    this.name = "taro";
  }
  say(){
    console.log( ` My Name is ${this.name} ` )
  }
}
class Dog extends MyClass{

}

function main(){

  let obj = new Dog();
  obj.say()

}

クラスを使った実行

クラスを使ってコードを書いたら、そのあとの関数で実行ができる。

f:id:takuya_1st:20210604033553p:plain

実行結果は次のようになる。

f:id:takuya_1st:20210604033543p:plain

続く

長いので分割しました。

次回に続く→08

             

         

 

Goole App Script ( GAS ) の基本的な操作方法。06. HTTPアクセスでJSON取得

前回の続き

前回までで、ハローワールドを書いて、ファイル分割して管理できるまでみた。

今回は、ネットアクセス

今回は、URLへリクエストを投げて、Google外部のAPIを使ってデータ連携する方法を見ておく。

ネットからデータを取得

URLを指定して、HTTPアクセスでデータを取得したり、データを送信できる。外部のAPIと連携して通知を送ったりできる。

URLへHTTP GET

HTTPでGET もできる。UrlFetchAppオブジェクトがグローバル空間に存在する。UrlFetchAppを使えばオッケ

function  getCurrentIp(){
  let url =  'https://api.ipify.org?format=json'
  let json = UrlFetchApp.fetch(url);
  let obj = JSON.parse(json);
  console.log(obj)
}

HTTPアクセスの結果例

f:id:takuya_1st:20210604052453p:plain

ちなみに、IPアドレスを取得すればわかるんですが、スクリプトは自分のPCで動いてるんすね。 てっきりリモートで動いていると思ってました。

URLにPOSTしてデータを取得 / HTTP POST

fetch の第2引数を指定すれば、POSTを使える。method にPOSTを入れるのですが、POST・GET以外は使えないようですね。

function  getCurrentIp(){
  let url =  'https://api.ipify.org'
  let options = {
  'method' : 'post',
  headers: { "Content-Type": 'application/json' },
  'payload' : 'format=json'
  };
  let json = UrlFetchApp.fetch(url,options);
  let obj = JSON.parse(json);
  console.log(obj)
}

続く

長いので分割しました。

次回に続く→07

Goole App Script ( GAS ) の基本的な操作方法。05. 複数ファイルに分割

前回まで

前回までで、スクリプトを使ってスプレッドシート側に操作ができるようになった。

今回は、ファイル分割。

単純な作業であれば、1つのファイルに全部書いていけばいいのでしょう。 しかし、まともにスクリプトを書くなら、ファイルに分割して書きたくなります。

今回は、ファイル分割をしてスクリプトを使う方法を見ていきます。

関数ファイルを追加する。

最初に用意されるファイルはコード.gs ( 英語版は CODE.gs )がファイル名のデフォルトになっている。

好きなファイル名で追加することができる。

メニューからファイルを追加を選んで押す。

f:id:takuya_1st:20210604032805p:plain

スクリプトを選ぶ

f:id:takuya_1st:20210604032813p:plain

好きな名前でファイルを追加できる。拡張子は入力不要

f:id:takuya_1st:20210604032819p:plain

関数はグローバル空間に追加される。

作成したファイルは、起動前にすべてロードされる。そしてファイル中のグローバルな空間に関数として追加される。関数を追加するといつでも使えると思っていい。

関数以外のスクリプト

関数じゃなく、次のようにベタ書きしたスクリプトは、スクリプト起動前に実行される。

f:id:takuya_1st:20210604033016p:plain

これは、関数を実行する前に実行されます。タイミングはロード時です。関数実行前に、全ファイルをロードします。関数外のスクリプトは、このロード時に実行されるので注意。

ロード順=ファイル名の順

ロードの順序はファイルの順序(名前順)になる。ロード時に実行される関数を作るのであれば、ロード順にも配慮しないとだめ。

たとえば、同名の関数があれば後から読まれた関数で上書きされる。

ファイル名の順番でロードが後になるファイルで先のファイルに書かれた関数が、上書きされる。そのため後ファイルに置いた関数が優先される。

デフォルトの myFunction は、名前被りが多いです。ロード順に注意しないと、関数が実行されない。と悩む原因になります。

続く

長いので分割しました、次回に続く→06

      

takuya-1st.hatenablog.jp

        

  

   

Goole App Script ( GAS ) の基本的な操作方法。04.セルにハローワールド

前回の続き

前回までで、ハローワールド、関数を指定した実行、スプレッドシート側にハローワールドまでやった。

今回はドキュメント側に書き込む。

スプレッドシートにハローワールドを書き込んでみる。

スプレッドシートにハローワールドを書き込む。

スプレッド・シートのセルにハローワールドを書き込む。

function HelloworldToCell() {

  let app = SpreadsheetApp.getActiveSpreadsheet();
  let sheet = app.getActiveSheet();
  let range = sheet.getRange('A1');

  range.setValue("Hello World.");
}

書き込んだら、実行します。

f:id:takuya_1st:20210604032325p:plain

権限が付与されたら、セルにハローワールドが入力される。

f:id:takuya_1st:20210604032313p:plain

続く

長いので分割しました。次に続く→05

           

Goole App Script ( GAS ) の基本的な操作方法。03.ダイアログでハローワールド

前回の続き

前回までで、コンソールにログをとして出力する。

今回は、スプレッドシートと連携する。

スプレッドシートと連携してハローワールドをスプレッドシート側に出す。

スプレッド・シートに出力ーダイアログ

スプレッド・シートにハローワールドをダイアログとして表示する。

スクリプトエディタで、Browser.msgBox関数を書き込んでいく。
このとき、補完を体験しておく。

Broと打てば補完が効く。

f:id:takuya_1st:20210604031659p:plain

続けて msgBox で補完が効く。

f:id:takuya_1st:20210604031716p:plain

ダイアログを表示する関数を作った

f:id:takuya_1st:20210604031623p:plain

実行する

f:id:takuya_1st:20210604051146p:plain

実行ボタンを押して権限を承認。

実行すると、権限チェックを求められます。

スクリプトGoogle Spreadsheetにアクセスする権限が必要になります。

権限はそのまま許可してあげればオッケです

f:id:takuya_1st:20210604031413p:plain

ダイアログが表示される。

権限の画面遷移が終われば、ダイアログが表示される。

権限は、初回起動時のみで、次回以降は権限があるので確認されない。f:id:takuya_1st:20210604031415p:plain

実行者ごとに権限。

自分のアクセスに関するアクセス権限なので、別人物(別アカウント)が実行するときにはまた別に権限が必要になる。

スクリプトはローカル

スクリプトはあくまでJSなので、ローカルのブラウザ内部で実行され。そのために、リモートにあるGoogle Spreadsheetへアクセス権を要求する。などと考えたら理解しやすいと思う。

続く

長いので分割しました。次に続く→ 04

     

   

Goole App Script ( GAS ) の基本的な操作方法。02.関数の追加

前回の続き

前回はハローワールドを実行した。

今回は関数を追加する。

関数を追加して、実行する。

関数を追加する。

2つ目の関数を作って使う。関数を作って使うために、サクッと関数を作る。

f:id:takuya_1st:20210604031135p:plain

実行する関数を選ぶ

関数は実行時に選択する必要があるので、作った関数を選択する。

f:id:takuya_1st:20210604031240p:plain

関数を選んでから実行する

関数を選んでから実行ボタンを押す。

f:id:takuya_1st:20210604031247p:plain

実行ログ。

結果が出てくる。

f:id:takuya_1st:20210604031322p:plain

console.log は実行ログ。

console.log は、実行ログとしてスクリプト・エディタに表示される。スクリプト・エディタでの実行ログは初期ロード時に非表示なので、画面構成が変化 するのですぐ気づけると思う。

続く

長いので分割しました。次回に続く→ 次 03

     

 

  

Goole App Script ( GAS ) の基本的な操作方法。01.コンソールでハローワールド

GAS を始める。

GAS を始めるときに、最初に手順を覚える。

最初の流れ。

  1. Google Driveで新規スプレッドシートを作る
  2. スプレッドシートを開ける。
  3. ツールからスクリプト・エディタを開く
  4. ツールから実行する

この記事で取り扱うこと

  • スクリプトエディタの起動
  • ハローワールドの実行(コンソール)
  • 関数の追加と実行
  • スプレッドシートにハローワールド(ダイアログ)
  • スプレッドシートにハローワールド(セルに記入)
  • 複数ファイル分割
  • ファイルのロード順
  • 最初に実行される関数
  • メニューで実行
  • ボタンで実行
  • ショートカット

準備 / Google Driveで新規作成する

Google Driveで新規スプレッド・シートを作って、そのファイルを開く。

スプレッドシートを作り開く

右クリックメニューから新規、スプレッドシートを選んで、新規作成

f:id:takuya_1st:20210604025908p:plain

スクリプトエディタを開く

スプレッドシートが開いたら、メニューからスクリプトエディタを選択する

f:id:takuya_1st:20210604025922p:plain

スクリプト・エディタを別のウインドウにする

スクリプトエディタが別タブで開く。スクリプトエディタとスプレッドシートはペアで使う。

別ウインドウにしておいたほうが、動作チェックが楽。たくさん試すときは、別ウインドウにしておくほうがいい。

f:id:takuya_1st:20210604025932p:plain

別ウインドウで開いておけば、スクリプトを実行したときにすぐに結果がわかる。視覚的にわかるので便利。

f:id:takuya_1st:20210604025935p:plain

GAS でハローワールド

console.log でハローワールドを書く。

f:id:takuya_1st:20210604030401p:plain

実行ボタンを押す。

上部メニューから、実行を押す。

f:id:takuya_1st:20210604030403p:plain

下部に実行結果が出てくる

下部の実行ログの部分に、実行結果が表示される。

f:id:takuya_1st:20210604030550p:plain

続き

長いので、分割しました。

次に続く→02 

  

  

potainer でユーザとチーム毎に管理できるホスト(endpoint)を設定する。

ユーザ毎にアクセス権(利用可不可)設定する。

Portainer は「Role単位の詳細権限」については、Bussiness版が必要なのだが、

チーム(グループ)とユーザ毎に、このDockerホストを許可する許可しないと設定できる。

少しわかりにくかったのでメモ。

f:id:takuya_1st:20210531153354p:plain

Endpoints の右カラムから行う。

Portainerに登録された、エンドポイント(サーバー)を1単位として、ユーザにアクセス権を設定できる。 これで、複数のPotainerを起動しなくて済ませられるのは熱い。

f:id:takuya_1st:20210531153813p:plain

アクセス権とユーザー単位で分けておけば、不意に他の人のインスタンスやサーバーをダウンさせてしまうことが減るので便利ですね。

事前にエンドポイントを設定する必要がありますけど。エンドポイントはdocker in docker は docker in lxc などで増やしてしまえばいいので。

docker別ホストから接続、管理SockをTCP経由許可して利用する。

docker の管理を別ホストから行いたい

ほとんどの人はDockerが動いているマシンへ SSHで接続してるともう。

ssh 経由で docker を使う場合

作業用PC ----<SSH>---- docker-host

リモートのDockerがインストールされたマシン中でdocker コマンドを叩いている場合

接続の詳細。

実際には接続がUNIX のソケット経由になっている。

作業用PC ----<SSH>---- docker-host----<unix:/socket>----/var/run/docker.sock

ん?unix/socket?そうですね。ソケットファイル経由です。
「だったら、ファイルじゃなくポートをリッスンすれば直接つながるのでは?」と思った貴方は大正解です。

tcp 接続でダイレクトに接続できる。

docker は TCPリッスンできるので、socket を経由しなくてもダイレクトに接続ができる。

作業用PC --------------- tcp ----------------- docker-host

SSH経由に比べると、ポートは開きっぱなしだし、認証関係もすっ飛ばすし、危険性が高いように思えるかもしれないが 仮想マシン間、開発マシンと仮想マシン、ローカル内部でこれを使うとサクッと接続できるので、便利です。

Docker のTCP接続を有効にする

-H を使って tcp へのリッスンを追加する。

/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock -H tcp://0.0.0.0:2375

ただしfd を消しちゃうと socket ファイル経由で接続ができなくなる。

ubuntu の docker にTCPをリッスンさせる

ubuntu で apt install docker.io でインストールした apt のdocker の場合、起動管理をsystemd が行っているのでsystemd のサービスファイルを書き換える。

sudo vim /lib/systemd/system/docker.service

該当サービスの ExecStartに -H tcp://0.0.0.0:2375 を追記する。

ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock -H tcp://0.0.0.0:2375

systemd をリロードして docker を再起動する

sudo systemctl daemon-reload
sudo systemctl restart docker

これでTCP経由で接続ができる。

クライアント側から docker接続する

docker コマンドは、DOCKER_HOST 変数で接続先を任意に指定することができる。

DOCKER_HOST=tcp://192.168.100.100:2375 docker ps

または、継続的に変数に入れてあげる。

export DOCKER_HOST=tcp://192.168.100.100:2375
docker ps 

これで、docker を任意のマシンからSSHすることなく操作ができる。

2375 はデフォルトのポートでWindowsのDockerが初期インストール時にデフォルトに設定されていたので。同じものにしました。

portainer / windows docker で使われています。

この機能、portainer や docker windowsで公式に使われています。注意深く見ていると気づいたと思います。

この公式機能であるポートリッスンを使えば、いちいちSSHしなくていんですよね。

portainer などと組み合わせる。

portainer など docker 管理のシステムと組み合わせるときにどうしてもこれらの設定が必要になってくる。

ポート管理に注意

ローカル中のdocker であれば、権限は ユーザーごとに決めることができるが、TCPをリッスンしてしまうと制御不能なので、取り扱い注意ですね。*1

参考資料

How do I expose the docker API over TCP? - Server Fault

*1:前時代のtcpwrapperやxinetd的な systemd.socketを間に挟んでゴニョゴニョすればできるんだろうけど

docker で systemdが動く ubuntu イメージを作って遊ぶ-その2

前回、systemd を動かした。

前回の続き

docker の起動コマンドを /sbin/init にすれば、 docker の ubuntu でも systemdを使って遊べることがわかった。

単純な作業だったので、dockerfile でイメージ化して遊ぶ

dockerfileを作って遊ぶ

FROM ubuntu:20.04
RUN apt-get update
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends init

ENTRYPOINT ["/sbin/init"]

ビルド

dockerfileをビルドしてイメージにする。

docker image build -f Dockerfile -t ubuntu-systemd .

イメージを起動

コンテナは特権コンテナのほうが楽だけど、他に同じようなことをやってるひとを github見つけた。 その人の使ってるセキュリティ起動オプションを使う。

docker run -d   \
  --security-opt seccomp=unconfined \
  --tmpfs /tmp \
  --tmpfs /run \
  --tmpfs /run/lock \
  -v /sys/fs/cgroup:/sys/fs/cgroup:ro \
  -t ubuntu-systemd:latest

無事に起動することがわかる。

確認

nginx をインストールして systemd 経由でちゃんと起動するか確かめてみる。

docker exec -it 56e -- apt-get install nginx  --no-install-recommends
docker exec -it 56e systemctl status nginx
docker restart 56e systemctl status nginx
docker exec -it 56e systemctl status nginx

nginx が再起動後に起動することがわかる。

takuya@ubuntu:~/ubuntu-systemd$ docker exec -it 56e systemctl status nginx
● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
     Active: inactive (dead)
       Docs: man:nginx(8)
takuya@ubuntu:~/ubuntu-systemd$ docker exec -it 56e systemctl start nginx
takuya@ubuntu:~/ubuntu-systemd$ docker exec -it 56e systemctl status nginx
● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
     Active: active (running) since Tue 2021-05-25 17:16:51 JST; 1s ago
       Docs: man:nginx(8)
    Process: 611 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
    Process: 612 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
   Main PID: 613 (nginx)
      Tasks: 5 (limit: 1388)
     CGroup: /system.slice/snap.docker.dockerd.service/system.slice/nginx.service
             ├─613 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
             ├─614 nginx: worker process
             ├─615 nginx: worker process
             ├─616 nginx: worker process
             └─617 nginx: worker process

May 25 17:16:51 56e5331be46b systemd[1]: Starting A high performance web server and a reverse proxy server...
May 25 17:16:51 56e5331be46b systemd[1]: Started A high performance web server and a reverse proxy server.
takuya@ubuntu:~/ubuntu-systemd$

まとめ

/sbin/init もちょっとしたことで動くんだね。

でも公式でやってないということは・・・

遊べることがわかったけど、公式でやってないということは。何らかの問題があるのでサポートしてないんだろう。

また、先人がやってないということは、この手法を突き詰めることは徒労に終わる可能性が高いので、遊ぶのはここまで。

systemd は本当に多機能なので、直接使うと無駄が多いとか、そういう理由で supervisord を使ったりするかも。

docker の設計思想がシングルプロセスなのでdocker-composeを使うんですよ。きっと

例えば systemd-multiuser.target

マルチユースなマルチユーザーを前提にした multiuser や logind はいらないよね。

そう考えるとやっぱり無駄が多そう。

例えばシャットダウン

シャットダウンはできない。

root@db1ccf6f38c6:/# ps auxf
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         678  1.0  0.0   4108  2540 pts/1    Ss   18:30   0:00 bash
root         686  0.0  0.0   5896  2140 pts/1    R+   18:30   0:00  \_ ps auxf
root           1  0.5  0.0  16308  5020 ?        Ss   18:27   0:01 /lib/systemd/systemd-shutdown reboot --timeout 90000000us --log-level 6 --log-target kmsg --log-color

シャットダウンできるわけでもない。

次のようなsystemctl が使いたいだけにはヘビィすぎる。

systemctl restart nginx 

systemd は本当に巨大なので、必要な部分だけ取り出して使おうとすると、ちょっとめんどくさいですよね。

追記 Windows Dockerで動かした場合

WSL2のWindowsなDockerで動かした場合、特権なしで動きました。

もしかして世間にあふれるブログ情報でsystemd / init を動かしたと書かれていたらWindowsでの話かもしれません。

WindowsのDocker (WSL2・Hyper-V)で動かした場合に特権なしで動いている例。 特権なしの代わりに sys_admin のCapabilityをADDしています。

参考資料

GitHub - bdellegrazie/docker-ubuntu-systemd: systemd-enabled versions of Docker base images

https://stackoverflow.com/questions/39169403/not-able-to-use-systemd-on-ubuntu-docker-container

dockerで systemd が動く ubuntu を作って遊ぶ - それマグで!

dockerで systemd が動く ubuntu を作って遊ぶ

はじめに

docker ではシングル・プロセスが前提なので、複数のプロセスを起動するべきではない。

それでも、ちょっと動かしてみたいと思うのが、遊びゴコロってやつ。docker で systemd を動かしてみたらどうなるのか。

今回は、Raspi4 の高速マシンがあるから遊んでみようと思います。

前提

docker がインストールされたホストOS。

完全仮想化か物理ホスト上で試す。

仮想化中のdockerなどは権限設定が大変なので考慮しない。 docker in docker だとか、 docker in lxd とかは動作がおかしくなる可能性があるんで、今回は考慮しない。

準備 raspi に docker インストール

素の ubuntu に、 docker をインストール。

最初に、ubuntu for raspberry pi 4 を持ってきて、raspiに ubuntuをしている。この素のubuntu に docker をインストール

snap 経由で docker をインストールする。

sudo snap install docker 

snap のdocker は一般ユーザからコマンドを使えないので、グループを追加して、パーミッションを変えて一般ユーザでもdocker コマンドを扱えるようにする。

自分自身のユーザをdocker グループに追加する。 groupadd と usermod でぱぱっと

sudo groupadd docker
sudo usermod -aG docker $USER

docker の socket ファイルの権限を chown で変えておく。

sudo chown root:docker /var/run/docker.sock
sudo chown "$USER":"$USER" /home/"$USER"/.docker -R
sudo chmod g+rwx "$HOME/.docker" -R

docker サービスを再起動。

sudo reboot 
## または
systemctl restart docker.service

これで準備が完了。

ubnutu(systemd)な-docker を作る 全体の流れ

全体の流れはざっくりいうと、systemd をインストールしてイメージ化、その後に起動コマンドをsystemdの /sbin/init にして作成したイメージを起動する

  • ubuntu を起動する
  • systemd をインストール
  • イメージとして保存
  • イメージを起動(起動コマンドは /sbin/init)

イメージを作成するのがちょっとしたポイントだと思う。

systemd をdockerで使うポイント

docker で ubuntu を systemd で起動する。これをするにはいくつかのポイントがある。

  • docker ubuntu では systemd が含まれてない。
  • systemd を起動するには権限が必要
  • systemd は /sbin/init で起動する
  • docker は起動するプロセスを指定する
  • docker run で起動するプロセスは /sbin/init にする

これらのポイントをちょっとだけ見ておく。

systemd は /sbin/init の代替として /etc/init.d から upstart から 置き換えられたプロセスで PID=1としてすべてのプロセスの祖先になる。

docker は 敢えて /sbin/init を外すことでシステム起動をプロセス単位に分離している。その代わりに docker run で起動するコマンドをしていて起動する。

これらを考慮すると、何らかの形で /sbin/init をインストールしたイメージを作り、docker run で /sbin/init を指定すれば起動するのではないかと思われた。

systemd で起動する ubuntu イメージを作る。

最初にすることは systemd init がインストールされたUbuntu を作りイメージとして保存し、イメージとして取り込む。

#

最初に、ubuntu をdocker で起動して、/sbin/init をインストールしてやる。

docker run --rm  -it  ubuntu:latest

ubuntu は起動コマンドを指定しないと bash が起動する。

起動した ubuntu で、systemd をインストールする

apt update 
apt install init 

この状態で、ログインしたまま起動状態を維持ておく。

別のターミナルを立ち上げて、起動中のdocker インスタンスをイメージとして取り出す。

別ターミナルからイメージ作成

docker 起動中のコンテナIDを調べて、tar ball ファイルとして取り出す。

docker container export bcdfc201cd09 > ubuntu-init-installed.tar

イメージが作成できたら、initをインストールしたインスタンスは不要なのでbashをCtrl-Dで終了しインスタンスを消しておく。

イメージを取り込む。

イメージが作成できたら、作成したファイルをdocker image として取り込む tar のファイルを イメージとしてインポートする。

docker image import ubuntu-init-installed.tar  takuya/ubuntu-init-installed:latest

takuya/ubuntu-init-installed:latest は イメージの名前とTAG を指定してる。イメージ名がtakuya/ubuntu-init-installedで、タグがlatest である。

systemd を指定して、docker イメージを起動

インポートしたイメージを起動する。

docker は指定したプロセスを起動するのので、systemd 経由で起動するように、/sbin/init を指定して起動する。

ただし、/sbin/init はインタラクティブシェルを提供しないので、 デタッチで起動する。

また、ここで 特権コンテナ--privilegedを指定する 。特権をコンテナでないとsystemd は動いてくれない。

docker run --privileged --rm -d takuya/ubuntu-init-installed /sbin/init

起動の確認

特権をコンテナで無事に起動すると dbus などが使えるので通常通りのsystemdなubuntu とほぼ同じ状態で起動する。

ちゃんと/sbin/init がPID=1で起動しているのがわかる。

 docker exec -it 356f77ee1bb1 ps auxf
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root          51  0.0  0.0   5468  2452 pts/0    Rs+  03:29   0:00 ps auxf
root           1  3.6  0.1 165800  9244 ?        Ss   03:29   0:00 /sbin/init
root          21  1.4  0.1  34648 10488 ?        S<s  03:29   0:00 /lib/systemd/
systemd+      33  0.9  0.0  20964  6240 ?        Ss   03:29   0:00 /lib/systemd/
systemd+      34  0.9  0.0  89940  3704 ?        Ssl  03:29   0:00 /lib/systemd/
message+      37  0.1  0.0   7948  3596 ?        Ss   03:29   0:00 /usr/bin/dbus
root          39  1.6  0.2  26124 16820 ?        Ss   03:29   0:00 /usr/bin/pyth
root          41  0.6  0.0  16028  6124 ?        Ss   03:29   0:00 /lib/systemd/
root          45  0.0  0.0   2344  1484 ?        Ss   03:29   0:00 /sbin/agetty

あっさりと起動した。ずいぶんと拍子抜けである。 docker ではマルチなプロセスを管理するべきではないと言われて久しいが、動くものは動くのであるな。

nginx / ssh が有効になったイメージを作ってみる。

nginx や ssh が有効になったイメージを作ってみる。

systemd がインストールされた ubuntu を作れたので nginx / ssh をインストールして 起動と同時に ssh と nginx が起動するイメージの作成を試みる。

systemdなubuntuを起動して

docker run --rm --privileged -d takuya/ubuntu-init-installed  /sbin/init
docker exec -it 8f72eab2c5be  apt install openssh-server nginx

イメージとして取り出してインポート

docker export 8f72eab2c5be > ubuntu-init-installed-with-ssh-nginx.tar
docker image import ubuntu-init-installed-with-ssh-nginx.tar  takuya/ubuntu-init-installed:ssh
docker run --rm --privileged  -d takuya/ubuntu-init-installed:ssh  /sbin/init

チェックすると。問題なく動きますね。

 docker exec a2ef6147fdc systemctl status nginx
● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
     Active: active (running) since Tue 2021-05-25 03:54:03 JST; 22s ago
       Docs: man:nginx(8)
    Process: 40 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
    Process: 48 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
   Main PID: 52 (nginx)
      Tasks: 5 (limit: 9257)
     CGroup: /docker/a2ef6147fdcc209a31cd1774234b461c20a85c9540e29b910089e5a44935fe20/system.slice/nginx.service
             ├─52 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
             ├─53 nginx: worker process
             ├─54 nginx: worker process
             ├─55 nginx: worker process
             └─56 nginx: worker process

May 25 03:54:03 a2ef6147fdcc systemd[1]: Starting A high performance web server and a reverse proxy server...
May 25 03:54:03 a2ef6147fdcc systemd[1]: Started A high performance web server and a reverse proxy server.

あっさり動いてしまうんですね。

特権コンテナに注意。

特権コンテナは docker in docker のような特殊な用途であったりする目的のためにある。

あれこれ細かいところは省きますが、特権コンテナを使うので公開サーバーに利用は控えること。

用途

docker で apt を試したいときや、仮想マシンを起動するほどもでもない開発環境で export してぱぱっと渡したいとき。 初心者が多い開発チームなど、トラブルを回避するために一時的に export して import させてぱぱっと開発環境を再現させたり。とか便利そう。

ちょっと開発環境でインストールを試したいときとかそういうときにも便利そうですね。

dockerfile

動作の仕組みさえわかれば、dockerfile で systemd を書くことも不可能ではなさそう。私は開発環境でちょっと試したいとかなのでdocker export で満足しているので試さなかった。

まとめ

/sbin/init をインストールした状態で privileged で起動すると動かせる。

raspi4 8GB はマジ高性能なので実験環境に最適だった。

ただし、arm(aarch64)なraspiからx86_64 なamdは相互にexport/import出来ないはずなのでそのへんはちょっと注意。

特権コンテナには十分に注意しないといけない。特権コンテナは通常の物理ホストと何ら変わりなくdocker の仕組み上プロセスやファイルが分離されているようなことはない わかりやすいトラブルとして、 このコンテナが乗っ取られたときに、 docker 内部 docker をインストールされてしまい、docker run -v /:/real-hostと内部でマウントされてしまうと目が当てられない。

参考資料