systemd - .timer 編

こんにちは、masm11 です。

systemd シリーズの第5回、最終回です。

f:id:masm11:20201202230535p:plain

初回:

前回:

今回は .timer ファイルについて説明します。

.timer ファイルとは

以前から Linux/UNIX を管理している方には、cron の代わり、と言うと 話が早いかと思います。前回比較した inetd よりご存知の方は多いのでは ないかと思います。

この unit ファイル中に指定されている日時に、指定されているサービスを 起動するのです。

.timer ファイルの書き方

[Unit] セクション

.timer ファイルも unit ファイルの一種なので、[Unit] セクションが あります。

[Unit]
Description=ZFS replication timer

こんな感じで良いかと思います。.service の場合と同様、Description は ログ出力の際などに使われます。

[Timer] セクション

.timer ファイル特有のセクションです。

OnCalendar=hourly

これで、1時間おき (毎正時) に service を起動してくれます。 hourly の他に daily や weekly なども指定できます。

でも、毎正時っていろんな処理が起動して負荷が高くなりがちですよね。 そういう時にはこれです。

AccuracySec=300

指定するのは秒数です。OnCalendar で指定した時刻から、ランダムで AccuracySec までずらしてくれます。

Persistent=true

これを true にしておくと、前回 service を起動した日時をディスクに 保存しておいて、次に timer が start した際 (OS 起動時とか) に 「1回飛ばしてる!」と思ったら、その瞬間、service を起動してくれます。

Unit=foo.service

これは起動する service 名を指定します。 指定しなかった場合は、foo.timer のデフォルトは foo.service に なりますので、大抵の場合は指定しなくて良いと思います。

[Install] セクション

このセクションを書いておくと、systemctl enable できるようになります。

[Install]
WantedBy=timers.target

こう書いておくことが多いと思います。

.timer ファイルの書き方

そして、起動するサービスも定義しておく必要があります。 サービスですので .service ファイルに書きます。

[Unit]
Description=ZFS replication service
After=network.target
Requires=network.target

[Service]
Type=oneshot
ExecStart=/etc/systemd/zfs/zfsendsnap.rb zroot/home

ExecStart にサービスのプログラムを指定します。

[Install] セクションは不要です。

あとは、

sudo systemctl start foo.timer

とするだけで、指定日時になると service が起動します。

デバッグ

service を開発中で、timer 経由でなく service を直接起動したい場合もあるでしょう。

その場合は、

systemctl start foo.service

で起動することができます。

まとめ

今回は .timer ファイルの内容について説明しました。

ここまでの5回で、

  • systemctl の使い方
  • .service の書き方
  • unit ファイルの編集方法
  • .socket の書き方
  • .timer の書き方

について、具体例として私がよく使う使い方を交えながら説明してきました。 今回でこのシリーズは終わりです。

systemd はなんとも大きなシステムでとっつきにくいですが、 このくらいでも書けると、だいぶん systemd の世界に親しみを感じるようになります。

少しでも参考にしていただけたら幸いです。

では良いお年を!

【解決】nginx で gzip 圧縮がされない問題

f:id:kizashi1122:20201222093127p:plain

id:kizashi1122 です。 2020年ももう終わりですね。今年最後のエントリです。今年も色々ありました。

その色々あった中で nginx がらみで面白いことがあったのでシェアします。

弊社サービス Re:lation では Ruby on Rails を採用しており、AWS 上で稼働させています。 フロントには ALB をおいて、nginx をはさみ、unicorn が動いています。nginx は静的ファイルへのアクセスやHTTPのリダイレクト処理、固定ヘッダの追加など静的な処理を任せています。

nginx に任せている処理の中に gzip 圧縮があります。これは動的生成コンテンツも圧縮することができ、特にレスポンスがテキストの場合などは圧縮率が高くなり転送サイズはぐっと小さくなります。

現在はほとんどのサービスで gzip 圧縮が使われてると思います。

HTTP リクエストヘッダ

ブラウザがサポートしている圧縮アルゴリズムがあればリクエストヘッダに以下のように設定されます。 br というのは、Brotli というアルゴリズムで圧縮率が高いようです。

Accept-Encoding: gzip, deflate, br

HTTP レスポンスヘッダ

そして、圧縮をサポートしているならば、サーバー側は圧縮してもよいので圧縮して送ります。

content-encoding: gzip

圧縮されたレスポンスのヘッダはこんな感じになります。

nginx の設定で gzip 圧縮を有効にしたい場合は設定ファイルにこのような記述をします(最低限)。

  gzip              on;
  gzip_types        application/json text/plain text/css application/x-javascript text/xml application/xml application/rss+xml text/javascript application/javascript image/x-icon;

text/html は指定しなくてもデフォで圧縮がかかるようです。

さて

とある環境からどうも遅く感じるという報告があり、パフォーマンスのボトルネックを探ってたときにある状況を発見しました。 (そのとある環境からの問題はまだ解決してないのですが)

レスポンスによっては gzip 圧縮が効いてないようなのです。

そのリクエストは POST でした。ははーん、 POST だと効かないのか? いやそうではないようだ。ちゃんと POST のレスポンスでも圧縮されている。

なにが違うのか?

調べていった結果、HTTPのステータスコードが 201 (Created) の場合のみ圧縮が効いてないのです。 nginx のソースをみてみます。

https://github.com/nginx/nginx/blob/master/src/http/modules/ngx_http_gzip_filter_module.c#L228-L240

ここに gzip 圧縮フィルタの関数があります。 if 文をみると HTTP_OK (200) でも NGX_HTTP_FORBIDDEN (403) でも NGX_HTTP_NOT_FOUND (404) でもない場合は処理を抜けて次のフィルタ適用の処理に進んでしまうようです。

微妙だ。

解決策としては、ステータスコードを 201 から 200 に変えるだけ。 これで圧縮が効くようになりました。

めでたしめでたし。

IE11 対応をやめることで得られるメリット

f:id:ishiyu1125:20201222114120p:plain

こんにちは。石田( @ishiyu )です。

今回は、弊社サービスRe:lation のIE11対応終了を受けて、IE11対応を終了することでエンジニア視点で得られるメリットを紹介したいと思います。

1. WebP対応

個人的に一番大きいのは、これかなと感じています。

WebP は、Google が開発した新たな画像形式です。Edge, Firefox, Chrome, Safari の最新版ではすでに対応されており、従来の画像形式と比べてもかなり容量を抑えられます。

ja.wikipedia.org

また、IE11 ではPNG画像などにフォールバックする必要があり(帯域が狭い環境では)画像がなかなか表示されないなどの問題があるため、使用するユーザのメリットも大きいと思います。

2. CSS (::placeholder 擬似クラス)

CSS は「::placeholder 擬似クラス」がなかったので、一番辛く感じてました。よくカスタムプロパティがないという記事を見かけるのですが、SCSS(SASS)を使っていたため、自分は困ったことがありませんでした。
それに対して、::placeholder 擬似クラスはテキストボックスなどのプレースホルダーをデザインするために必要なセレクタで、これを代替しようと思うと JavaScript と組み合わせて実装せざるおえません。 幸い Re:lation で使用する機会はなかったのですが、もし対応しようと思うと非常に大変だったと思います。

CSS のその他の変更点はこちらです。
https://caniuse.com/?compare=ie+11,edge+87&compareCats=CSS

3. JavaScript (Promise)

JavaScript に関してはアロー関数や Array.find、fetch など他にもありますが、JavaScript の中で一番大きい影響は Promise と思っています。Promise は非同期ライブラリを入れれば解決する話ですが、わざわざ標準で対応されているものを今から入れることは、長い目で見ると技術的負債になります。

もちろん、Babel などを導入していれば、コードを記述する際にはあまり気にすることはほぼありません。 しかし、IE11対応を行うことでビルド時間や生成されるコード量がどうしても増えるので、開発中のビルド待ち時間が長くなり、スムーズな開発がしにくくなるデメリットもあります。(core-jsのメンテナの zloirock さんが無事に帰ってこれてホントよかった)

JavaScript のその他の変更点はこちらです。
https://caniuse.com/?compare=ie+11,edge+87&compareCats=JS

さいごに

色々書きましたが、ホントの一番大きいメリットはこれらを常に意識する必要がなくなるということではないでしょうか。 この運用コストはバカにならないと思っています。

IE11対応終了は、いろいろなお客様へサービスを提供するということとは相反することではありますが、新しい機能をよりスムーズに提供することで理解を得られるように、これからも頑張っていきたいと思います。

Rails 6.1 つまみ食い① : 関連付けの非同期削除

おはようございます!

公私ともに2020年を納めきれるか心配になってきた @shutooike です。

今回から数回に亘って Rails 6.1 の新機能を浅く広くつまみ食いしていこうと思います!

セットアップ

techracho.bpsinc.jp

こちらの記事を参考に dip を使って環境構築をしていきます。

$ git clone https://github.com/hachi8833/rails6_docker_quicksetup_sqlite3.git
$ mv rails6_docker_quicksetup_sqlite3/ test_rails_latest/
$ cd test_rails_latest/
$ rm -rf .git
$ git init
$ dip provision
.
.
.
Webpacker successfully installed 🎉 🍰
Creating test_rails_latest_backend_run ... done
yarn install v1.22.4
[1/4] Resolving packages...
success Already up-to-date.
Done in 2.08s.
Creating test_rails_latest_runner_run ... done
Creating test_rails_latest_backend_run ... done
$ dip rails s
Creating test_rails_latest_rails_run ... done
=> Booting Puma
=> Rails 6.1.0 application starting in development
=> Run `bin/rails server --help` for more startup options
.
.

f:id:shutooike:20201221024624p:plain
localhost:3000

Yay!Rails 6.1 の環境構築がものの10分でできました!dip 良さげ!

次に Post has many comments なモデルを scaffold で作成します。

$ dip rails g scaffold post title:string body:string
$ dip rails g scaffold comment post:references body:string
$ dip rails db:migrate
class Post < ApplicationRecord
  has_many :comments
end

class Comment < ApplicationRecord
  belongs_to :post
end

セットアップ完了です!

Destroy Associations Async

weblog.rubyonrails.org

今回試すのは「関連レコードをバックグラウンドジョブで destroy してくれる」新機能です。

アソシエーションが大量にあるレコードを destroy する場合はタイムアウトやパフォーマンスを気にして、自前でこのような実装しているところも多いと思います。

こういう機能をフレームワークがサポートしてくれるのは嬉しいですね!

では早速、先ほどの Post モデルにつけて

class Post < ApplicationRecord
  has_many :comments, dependent: :destroy_async
end

Post レコードを削除してみます。

irb(main):001:0> post = Post.first
  Post Load (6.3ms)  SELECT "posts".* FROM "posts" ORDER BY "posts"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<Post id: 1, title: "test", body: "1", created_at: "2020-12-20 17:07:49.130967000 +0000", updated_at: "2020-12-20 17:07:49.130967000 +0000">
irb(main):002:0> post.comments
  Comment Load (7.7ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? /* loading for inspect */ LIMIT ?  [["post_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Comment id: 1, post_id: 1, body: "comment 1", created_at: "2020-12-20 17:08:23.394683000 +0000", updated_at: "2020-12-20 17:08:23.394683000 +0000">, #<Comment id: 2, post_id: 1, body: "comment 2", created_at: "2020-12-20 17:09:03.999279000 +0000", updated_at: "2020-12-20 17:09:03.999279000 +0000">]>
irb(main):003:0> post.destroy
  TRANSACTION (0.8ms)  begin transaction
  Comment Load (15.0ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ?  [["post_id", 1]]
Enqueued ActiveRecord::DestroyAssociationAsyncJob (Job ID: 84f9208b-0e6d-46e9-a87e-17600e314c9c) to Async(default) with arguments: {:owner_model_name=>"Post", :owner_id=>1, :association_class=>"Comment", :association_ids=>[1, 2], :association_primary_key_column=>:id, :ensuring_owner_was_method=>nil}
  Post Destroy (45.6ms)  DELETE FROM "posts" WHERE "posts"."id" = ?  [["id", 1]]
  TRANSACTION (5.0ms)  rollback transaction
Traceback (most recent call last):
        1: from (irb):5
ActiveRecord::InvalidForeignKey (SQLite3::ConstraintException: FOREIGN KEY constraint failed)
irb(main):006:0>    (0.3ms)  SELECT sqlite_version(*)
Performing ActiveRecord::DestroyAssociationAsyncJob (Job ID: 84f9208b-0e6d-46e9-a87e-17600e314c9c) from Async(default) enqueued at 2020-12-20T17:12:21Z with arguments: {:owner_model_name=>"Post", :owner_id=>1, :association_class=>"Comment", :association_ids=>[1, 2], :association_primary_key_column=>:id, :ensuring_owner_was_method=>nil}
   (1.7ms)  SELECT sqlite_version(*)
  Post Load (12.1ms)  SELECT "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
Error performing ActiveRecord::DestroyAssociationAsyncJob (Job ID: 84f9208b-0e6d-46e9-a87e-17600e314c9c) from Async(default) in 137.57ms: ActiveRecord::DestroyAssociationAsyncError (owner record not destroyed):
/usr/local/bundle/gems/activerecord-6.1.0/lib/active_record/destroy_association_async_job.rb:23:in `perform'
/usr/local/bundle/gems/activejob-6.1.0/lib/active_job/execution.rb:48:in `block in perform_now'
.
.

/usr/local/bundle/gems/concurrent-ruby-1.1.7/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb:334:in `block in create_worker'

親の Post レコードが外部キー制約により削除できなかったため、ジョブで例外が発生しました。なんたるおバカムーブ、当たり前ですね 🤦‍♂️ 🤦‍♀️

外部キー制約を外して

class RemoveForeingKeyOnComment < ActiveRecord::Migration[6.1]
  def change
    remove_foreign_key :comments, :posts
  end
end
$ dip rails db:migrate

もう一度

irb(main):004:0> post.destroy
   (0.9ms)  SELECT sqlite_version(*)
Enqueued ActiveRecord::DestroyAssociationAsyncJob (Job ID: 8ac3ce06-aaaa-40c0-b40c-600028a99436) to Async(default) with arguments: {:owner_model_name=>"Post", :owner_id=>1, :association_class=>"Comment", :association_ids=>[1, 2], :association_primary_key_column=>:id, :ensuring_owner_was_method=>nil}
  TRANSACTION (1.6ms)  begin transaction
  Post Destroy (46.7ms)  DELETE FROM "posts" WHERE "posts"."id" = ?  [["id", 1]]
  TRANSACTION (52.5ms)  commit transaction
=> #<Post id: 1, title: "test", body: "1", created_at: "2020-12-20 17:07:49.130967000 +0000", updated_at: "2020-12-20 17:07:49.130967000 +0000">
irb(main):010:0>    (0.2ms)  SELECT sqlite_version(*)
Performing ActiveRecord::DestroyAssociationAsyncJob (Job ID: 8ac3ce06-aaaa-40c0-b40c-600028a99436) from Async(default) enqueued at 2020-12-20T17:26:22Z with arguments: {:owner_model_name=>"Post", :owner_id=>1, :association_class=>"Comment", :association_ids=>[1, 2], :association_primary_key_column=>:id, :ensuring_owner_was_method=>nil}
   (0.3ms)  SELECT sqlite_version(*)
  Post Load (6.6ms)  SELECT "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  Comment Load (36.2ms)  SELECT "comments".* FROM "comments" WHERE "comments"."id" IN (?, ?) ORDER BY "comments"."id" ASC LIMIT ?  [[nil, 1], [nil, 2], ["LIMIT", 1000]]
  TRANSACTION (3.8ms)  begin transaction
  Comment Destroy (54.9ms)  DELETE FROM "comments" WHERE "comments"."id" = ?  [["id", 1]]
  TRANSACTION (31.8ms)  commit transaction
  TRANSACTION (0.6ms)  begin transaction
  Comment Destroy (109.4ms)  DELETE FROM "comments" WHERE "comments"."id" = ?  [["id", 2]]
  TRANSACTION (37.8ms)  commit transaction
Performed ActiveRecord::DestroyAssociationAsyncJob (Job ID: 8ac3ce06-aaaa-40c0-b40c-600028a99436) from Async(default) in 502.18ms

今度はうまくいきました!

さいごに

destroy_async 名前がわかりやすくていいですね〜

もう少し動作の把握が出来たら個人の Rails プロダクトでも使ってみようと思います。

次回は ActiveStorage の permanent link を試すつもりです!

では良いお年を!

【追記】

第2回書きました!

blog.ingage.jp

第2回 Dockerのコンテナとファイル共有して継続的に開発する

f:id:ingnis:20201216161443p:plain

こんにちは、にしむらです。

今回はコンテナとファイルを共有する方法についてです。

前回のSinatraのサンプルですと、ソースコードを修正するたびにコンテナをビルドする必要がありました。 今回はホストのディレクトリをコンテナにマウントすることで、コンテナ起動中でもファイル変更が反映されるようにしていきたいと思います。

続きを読む

Amazon Linux2 でサクッとワイルドカードドメインの証明書を作成する

https://letsencrypt.org/images/le-logo-standard.png

id:kizashi1122 です。 LetsEncrypt でワイルドカードドメインの証明書を作ることがあり簡単にできたので記録しておきます。 弊社 masm11 の過去の記事である

blog.ingage.jp

と鬼のようにかぶってます。

前提

本記事では、「example.com および *.example.com に対する証明書を取得する」という目的になります。証明書を取得するところまで、です。Webサーバーへの設定は含みません。

前提として、example.com のドメインを所有しており、DNS は Route53 で管理しているものとします。

EC2 起動

Amazon Linux2 のEC2インスタンスを起動します。t2.micro でよいでしょう。

IAM Role の設定

上記記事では aws key/secret を /root/.aws/credentials に配置するパターンで紹介していましたが、キーは発行してしまうと管理も大変になるので、今回は IAM Role を作って、EC2 インスタンスに紐付けるというやり方でやってみましょう。 順番的には、ポリシーを作成してからロールを作成します。ロール作成時に先に作ったポリシーを設定することになります。

ポリシーの作成

以下の JSON でポリシーを作成します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "route53:GetChange",
                "route53:ListHostedZones"
            ],
            "Resource": "*"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": "route53:ChangeResourceRecordSets",
            "Resource": "arn:aws:route53:::hostedzone/<対象ドメインの Hosted zone ID>"
        }
    ]
}

名前は letsencrypt-route53-policy とでもしておきましょうか。

ロールの作成

  • ロールの作成
  • AWS Service -> EC2 を選択して、次へ
  • 先程作成した letsencrypt-route53-policy を選択して次へ、次へ
  • 名前は適当に letsencrypt-route53-role とつけて作成完了

EC2にロールをアタッチする

マネジメントコンソールからできます。 - 対象のEC2インスタンスを選択肢 Actions -> Security -> Modify IAM role を選択 - 先程作成したロール letsencrypt-route53-role を選択して保存

以上です。

必要プログラムの取得

以下、 ec2-user にて実行します。

sudo wget -r --no-parent -A 'epel-release-*.rpm' http://dl.fedoraproject.org/pub/epel/7/x86_64/Packages/e/
sudo rpm -Uvh dl.fedoraproject.org/pub/epel/7/x86_64/Packages/e/epel-release-*.rpm
sudo yum-config-manager --enable epel*
sudo yum install certbot -y
sudo yum install python2-certbot-dns-route53 -y

yum で全部揃うんですね。

証明書の発行(最初)

sudo certbot --debug certonly \
  --server https://acme-v02.api.letsencrypt.org/directory \
  --email 'developer@example.com' \
  --agree-tos \
  --no-self-upgrade \
  --dns-route53 \
  -d \*.example.com \
  -d example.com

しばらくすると

  • /etc/letsencrypt/live/example.com/fullchain.pem
  • /etc/letsencrypt/live/example.com/privkey.pem

が作成されます。 fullchain.pem にはサーバ証明書と中間証明書が concat されています。 privkey.pem は秘密鍵となります。

簡単ですね。

ただし有効期限は3ヶ月なので3ヶ月おきに更新する必要があります。2ヶ月を経過するまでは

Cert not yet due for renewal

と言われてしまいますが、2ヶ月を経過すると更新できます。その際はコマンドはもっとシンプルになります。

sudo certbot --no-self-upgrade --debug renew

今は諸事情があって letsencrypt を使っていますが、今後は Amazon の証明書を使うつもりです。 その際はまた記事を投稿しようと思います。

ではまた。

systemd - .socket 編

f:id:masm11:20201202230535p:plain

こんにちは、masm11 です。

systemd シリーズの第4回です。

初回:

前回:

今回は .socket ファイルについて説明します。

.socket ファイルとは

以前から Linux/UNIX を管理している方には、inetd の代わり、と言うと 話が早いかと思います。

この unit ファイル中に指定されているポートでリクエストを待ち受け、 リクエストがあったら指定されているサービスに接続を渡すのです。

サービス自身がポートを開いて待ち受けるのに比べると、メリットもあります。 サービスがポートを開いて待ち受ける場合、そのサービスを restart すると、 短い時間とは思いますが、リクエストを受け付けられない時間ができます。 .socket ファイルを使うと、サービスが動いていない間も systemd が 待ち受け続けていますので、その時間をなくすことができます。

この機能は socket activation と呼ばれることもあります。

.socket ファイルの書き方

[Unit] セクション

.socket ファイルも unit ファイルの一種なので、[Unit] セクションが あります。

[Unit]
Description=ZFS snapshot receiver socket

こんな感じで良いかと思います。.service の場合と同様、Description は ログ出力の際などに使われます。

[Socket] セクション

.socket ファイル特有のセクションです。

まず、ポートを指定します。

ListenStream=1234

このように ListenStream に数字で指定すると、TCP になります。 この例では TCP のポート 1234 で待ち受けます。

ListenStream=/tmp/foo.sock

このようにファイル名を指定した場合は、stream 型 unix domain socket になります。

datagram タイプもあります。

ListenDatagram=1234

このように ListenStream の代わりに ListenDatagram を使い、数字で指定すると、 UDP になります。この例では UDP のポート 1234 で待ち受けます。

ListenDatagram=/tmp/foo.sock

同様に datagram 型 unix domain socket です。

次に、unix domain socket の場合は、socket の所有者やパーミッションを指定します。

SocketUser=root
SocketGroup=root
SocketMode=0600

例えばこのように指定すると、所有者は root、グループも root で、パーミッションは 0600 になります。

また、stream 型の場合は、backlog を指定できます。

Backlog=5

これは何かと言うと、接続待ちの最大本数です。 接続リクエストがたくさん同時に来た場合に、すぐに接続完了を返せれば良いのですが、 忙しくて待たせてしまう場合があります。 上のように設定すると、最大5本までは待たせておくことができます。

この数字を大きくすると、たくさん待たせておくことができます。 逆に小さくすると、待たせることができなかった接続については、 接続拒否になってしまいます。

C言語的に言うと、

listen(sock, 5);

の、5 です。

そして、かなりの昔から 5 にしておくことが多いようですが、 .socket のデフォルトとしては 128 だそうです。

更に、stream の場合には、accept するかどうかも指定できます。

Accept=true

これを true にすると、接続リクエストがあるたびに、 systemd が接続を完了させた後、その接続をサービスに引き渡します。

false にすると、最初に接続リクエストがあった時に、 接続とともに、待ち受けている socket もサービスに引き渡します。 そしてそれ以後サービスが待ち受けます。

さて、Accept=false の場合には、サービスの指定が可能です。

Service=foo.service

この指定をしなかった場合は、foo.socket のデフォルトは foo.service に なりますので、大抵の場合は指定しなくて良いと思います。

[Install] セクション

このセクションを書いておくと、systemctl enable できるようになります。

[Install]
WantedBy=sockets.target

こう書いておくことが多いと思います。

.service ファイルの書き方

そして、引き渡す先のサービスも定義しておく必要があります。 サービスですので .service ファイルに書きます。

今回は、.socket ファイルで TCP で Accept=true を指定した場合の例を 挙げます。

[Unit]
Description=ZFS snapshot receiver service

[Service]
ExecStart=/home/service/zfsrecvsnap/zfsrecvsnap.sh
StandardInput=socket
StandardOutput=socket
StandardError=journal

ExecStart にはサービスのプログラムを指定します。

StandardInputStandardOutput で、標準入力と標準出力を socket に つないでいます。 また、プログラムでエラーが起きた場合の標準エラー出力は journal へ投げることに しました。journal へ投げると、journalctl で確認できます。

そして、この .service ファイルには [Install] セクションがありません。 .socket から起動されるので、systemctl enable する必要がないからです。

以上で、sudo systemctl start foo.socket すると待ち受けが始まり、 接続するたびにサービスが起動します。

注意

.socket ファイルはプログラムの仕様と密接な関係にあります。

既存の .socket ファイルを編集してポート番号を変更するくらいは 問題ないと思いますが、 Accept を変更してサービスが問題なく動作してくれるとは思えません。

変更の際には必ずドキュメントに当たってください。

まとめ

今回は .socket ファイルの内容について説明しました。

.socket ファイルは、実は FIFO にもキャラクタ型デバイスにも使えるんですね。 socket だけだと思っていました。

ではまた!