TORICO Tech ブログhttps://tech.torico-corp.com/blog/2024-03-19T07:20:17+00:00株式会社TORICO 技術開発チームのブログdnfでphpのバージョンを指定してモジュールでインストールする方法2024-03-11T04:10:39+00:002024-03-19T07:09:07+00:00工藤淳https://tech.torico-corp.com/blog/author/kudou/https://tech.torico-corp.com/blog/dnf%E3%81%A7php%E3%81%AE%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E3%82%92%E6%8C%87%E5%AE%9A%E3%81%97%E3%81%A6%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%81%A7%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95/<div class="page-body">
<h1 class="" id="8f586e2c-dbac-4be0-8dab-d920a36f2e26">dnfとは</h1>
<p class="" id="75d5d7d9-9037-4acf-ab00-23880eb4573c">dnf は yum の後継となるパッケージ管理コマンドです。</p>
<p class="" id="ad98de17-765e-4daa-b6a2-cb049b109ea8">Dandified Yum の略で yum から派生して速度の改善がされていたり、モジュールの切り替えの簡易化などの機能が追加されています。</p>
<p class="" id="0bc05ffc-bf7f-4221-96f3-652962383022"></p>
<h1 class="" id="19152f23-2bbc-4c57-8008-82977cd6ba2d">実際に使ってみる</h1>
<p class="" id="51eefa46-e4c3-44bd-a33e-d5033355419e">今回はRedHat UBIで環境を構築して、新しいバージョンのphpを使えるようにしました。</p>
<p class="" id="f0d183cd-a8a5-4589-aa8f-6fa535e031b8">そのままのリポジトリだとPHPのバージョンが<code>7</code>なので、dnfを使用して<code>8</code>系をインストールします。</p>
<h2 class="" id="0e23d010-4ff9-457a-83c9-6183c54271e9">dockerコンテナを準備する</h2>
<p class="" id="205d55bf-1aad-4ab1-8e52-cc9504162139">docker hub から redhat/ubi8 のコンテナを取得</p>
<pre class="code" id="aca5f1d0-d98a-4bdd-ae28-84cd4fa4c9b8"><code class="language-Bash">docker pull redhat/ubi8:latest</code></pre>
<p class="" id="cf20988b-67ef-4ada-8390-627f45442011">コンテナを起動してシェルスクリプトに入る</p>
<pre class="code" id="7228f13c-aac7-48b2-b034-fc1df9c863f4"><code class="language-Bash">docker run -it redhat/ubi8:latest</code></pre>
<h2 class="" id="916ebb8c-bedb-4730-abb7-22c5e80dc820">dnfを使ってパッケージの確認</h2>
<p class="" id="bc38515b-0832-42da-9960-9c07b4f633ab">phpがインストールされているか確認する</p>
<pre class="code" id="13882d88-edec-474b-ba9a-8c65ba06a76f"><code class="language-Bash">dnf list installed</code></pre>
<p class="" id="ee296f40-11ee-4df9-aba9-80c56b3edbdf">インストールされていなかったので、いまのリポジトリで使えるPHPを確認する</p>
<pre class="code" id="f7d5867d-503f-4e6c-9cdd-10e815600156"><code class="language-Bash">dnf list php*</code></pre>
<p class="" id="29e9b3f8-690a-4c64-a135-5e04d99f5ece"><code>7.2.24</code> はありますが、新しいバージョンである<code>8</code>はありませんでした。</p>
<p class="" id="22f5ea82-2fc5-4fc8-bdf3-11c41f0f780c"><code>8</code>をインストールをするためにリポジトリを追加します。</p>
<p class="" id="c109b431-dca3-4b3c-8166-cc28b0e0ee44">現在のリポジトリを確認</p>
<pre class="code" id="dcc810c5-29b7-4d71-9657-d5034c923d1f"><code class="language-Bash">dnf repolist --all</code></pre>
<p class="" id="a8f3a344-604a-4dbe-9ee8-7bfa2c46374e">今回使いたいリポジトリは <code>epel</code> と <code>remi</code>。<br/>現在のリポジトリには含まれていなかったのでリポジトリを追加します。</p>
<p class="" id="e1c20d76-018a-4976-8813-59ef8f248a6e">epelリポジトリを追加。</p>
<pre class="code" id="9c2eee01-8514-4e42-addf-78429464d93a"><code class="language-Bash">dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm</code></pre>
<p class="" id="7e31af59-0e3a-468b-81ea-486b4c5c2a0f">remiリポジトリを追加。</p>
<pre class="code" id="9f790553-b78a-4175-8701-3ad686ad9013"><code class="language-Bash">dnf install -y https://rpms.remirepo.net/enterprise/remi-release-8.rpm</code></pre>
<p class="" id="9f15b63e-95d5-4ee2-a4d4-e6d583d24f60">改めて追加したリポジトリも含めて使えるPHPを確認する</p>
<pre class="code" id="6bc090f9-ec82-4e67-bf36-51d5e4da5531"><code class="language-Bash">dnf list php*</code></pre>
<p class="" id="0284d8ec-8357-44a9-8a2f-877e75f1cc67">結果</p>
<pre class="code" id="b6ec4d99-4214-477b-a992-4ba1a9923042"><code class="language-Bash">5.6
7.2
7.3
7.4
8.0
8.1
8.2
8.3</code></pre>
<p class="" id="846382c7-37c5-4fc1-a017-3d6498dbce56"><code>8.3</code>までのバージョンが使えるようになりました。</p>
<h2 class="" id="4e117051-f14b-497e-a8b7-0e29e46f7aee">バージョンを指定してモジュール形式でインストール</h2>
<p class="" id="f39cebb9-edf6-46ab-8e38-6b1221762fc4">ここで yum であればそのまま</p>
<pre class="code" id="f4a0f4b7-ca4b-4d3f-8e8c-f62a6f745210"><code class="language-Bash">yum install php83</code></pre>
<p class="" id="04530c17-57da-4aac-a01b-cec07bd01c7c">のコマンドを入力してパッケージを追加しますが、追加したremiリポジトリからのインストールだと通常時と違いremiフォルダにインストールされます。</p>
<p class="" id="8439d527-e4d8-4ff8-b9dc-5faf70a53e15">今後のことを考えるとフォルダが違うのはあまり望ましくありません。</p>
<p class="" id="a2d55893-1d4f-47cf-ac7b-b5994c283386">そこでdnf にはモジュールとして、パッケージのバージョンを指定する方法があります。</p>
<p class="" id="c6ecf434-624f-43eb-b427-bc1772d7301f">モジュールとパッケージのリストを表示</p>
<pre class="code" id="741df4f4-b3b3-48a4-b119-9c0203ae6dfe"><code class="language-Bash">dnf module list php</code></pre>
<p class="" id="746857b2-d22d-4d9f-884e-9ecb0b3b02d8">下記のようなリポジトリとパッケージのリストが表示されます。<br/>現在は<br/><code>7.2</code>に<code>[d]</code>がついているので、基本リポジトリの<code>7.2</code>がデフォルトとなっています。</p>
<pre class="code" id="5f95609d-e6d1-4907-9272-6396c03b9717"><code class="language-Bash">Remi's Modular repository for Enterprise Linux 8 - aarch64
Name Stream Profiles Summary
php remi-7.2 common [d], devel, minimal PHP scripting language
php remi-7.3 common [d], devel, minimal PHP scripting language
php remi-7.4 common [d], devel, minimal PHP scripting language
php remi-8.0 common [d], devel, minimal PHP scripting language
php remi-8.1 common [d], devel, minimal PHP scripting language
php remi-8.2 common [d], devel, minimal PHP scripting language
php remi-8.3 common [d], devel, minimal PHP scripting language
Red Hat Universal Base Image 8 (RPMs) - AppStream
Name Stream Profiles Summary
php 7.2 [d] common [d], devel, minimal PHP scripting language
php 7.3 common [d], devel, minimal PHP scripting language
php 7.4 common [d], devel, minimal PHP scripting language
php 8.0 common [d], devel, minimal PHP scripting language
Hint: [d]efault, [e]nabled, [x]disabled, [i]nstalled</code></pre>
<p class="" id="59a58a1e-99e5-4b2c-a773-0348d0de95e9">remiリポジトリの<code>8.3</code>のモジュールをインストールします。</p>
<pre class="code" id="34b59bae-c585-497d-bf10-e9b8af3f29ae"><code class="language-Bash">dnf module install php:remi-8.3</code></pre>
<h2 class="" id="fdad330a-9712-485f-95d6-bb36aec20776">インストール結果の確認</h2>
<p class="" id="fe279a31-64e1-40aa-a263-f961bf00e7df">現在のモジュールとパッケージのリストを表示</p>
<pre class="code" id="dcbd2e20-81eb-40cf-8cbb-6db7101e7c81"><code class="language-Bash">dnf module list php</code></pre>
<p class="" id="3ee672c2-d12e-4ee0-b46e-179479fd821e">remiリポジトリの<code>8.3</code>に<code>[e][i]</code>がついていいます。</p>
<pre class="code" id="fc06b236-62c3-4d5b-916f-95bd4c089329"><code class="language-Bash">Remi's Modular repository for Enterprise Linux 8 - aarch64
Name Stream Profiles Summary
php remi-7.2 common [d], devel, minimal PHP scripting language
php remi-7.3 common [d], devel, minimal PHP scripting language
php remi-7.4 common [d], devel, minimal PHP scripting language
php remi-8.0 common [d], devel, minimal PHP scripting language
php remi-8.1 common [d], devel, minimal PHP scripting language
php remi-8.2 common [d], devel, minimal PHP scripting language
php remi-8.3 [e] common [d] [i], devel, minimal PHP scripting language
Red Hat Universal Base Image 8 (RPMs) - AppStream
Name Stream Profiles Summary
php 7.2 [d] common [d], devel, minimal PHP scripting language
php 7.3 common [d], devel, minimal PHP scripting language
php 7.4 common [d], devel, minimal PHP scripting language
php 8.0 common [d], devel, minimal PHP scripting language
Hint: [d]efault, [e]nabled, [x]disabled, [i]nstalled</code></pre>
<p class="" id="77b218c4-b7af-4ce9-9c4e-6bcde82ec305">バージョンを表示。</p>
<pre class="code" id="c4c1afeb-23ea-4997-be08-a402e56c6276"><code class="language-Bash">php -v</code></pre>
<p class="" id="cb6836cb-54d4-4859-a9e0-182e38449bc0"><code>8.3</code>がインストールされてることが確認できます。</p>
<pre class="code" id="8e04ec44-699c-41e4-b998-38155a4b69ec"><code class="language-Bash">PHP 8.3.3 (cli) (built: Feb 13 2024 15:41:14) (NTS gcc aarch64)
Copyright (c) The PHP Group
Zend Engine v4.3.3, Copyright (c) Zend Technologies</code></pre>
<h1 class="" id="72c25453-5b2f-4b4d-8537-f73aa5fbb8fa">dnfのメリット</h1>
<p class="" id="de7cc438-4347-4165-9be6-68ac73ff3307">phpだけではなくapache、nginx、php-fpmなど他のパッケージでもモジュールでインストールすることができます。</p>
<p class="" id="99026019-1748-4071-886f-f6308bd1ba63">本番環境の構築だけでなく、他の環境をPCでローカル環境として再現する場合などdnfは便利なパッケージ管理コマンドです。</p>
<p class="" id="234fdd23-6105-43bb-bd2c-f9e1d368801e">また、モジュール形式でインストールすると有効なバージョンの切り替えもできるらしいので、これはありがたい機能。</p>
<pre class="code" id="ea9160fb-f50c-4b9e-9f0c-b37e4d8098ad"><code class="language-Bash">dnf module switch-to module:version</code></pre>
<p class="" id="31a45295-5e05-499a-8df2-a89ab912aea9"></p>
</div>CSS のフレックス(display: flex) でサイトをレイアウトするトレーニング2024-03-10T11:02:21+00:002024-03-19T05:24:03+00:00四柳剛https://tech.torico-corp.com/blog/author/yotsuyanagi/https://tech.torico-corp.com/blog/html-css-flex-page-layout-training/<p>当社TORICO の社内勉強会で CSS のフレックス( <code>display: flex</code> ) の演習を行いました。 この記事は、その勉強会で行ったフレックスの演習の内容を公開するものです。</p>
<h2 id="演習の内容">演習の内容</h2>
<p>HTML のウェブアプリとして、上部に固定ヘッダー、下部に固定フッター、中央左にナビ、中央右にメインコンテンツがあるサイトを想定して CSS を書きます。</p>
<p><img alt="" height="655" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/flex/blog-flex-01.png" width="959"/></p>
<p>CSS の書かれていない HTML に CSS を書いていき、目標画像と同じ見た目になるようにします。</p>
<p><a href="https://gist.github.com/ytyng/d83b840c334ac251816c038ac00e0280" target="_blank">CSS の書かれていない HTML のソースコード</a></p>
<p>なお、この記事ではフレックスの基本的な属性の意味については説明していません。 必要に応じて、<a href="https://developer.mozilla.org/ja/docs/Web/CSS/flex" target="_blank">MDN</a> 等を見てください。</p>
<h3 id="完成した-html">完成した HTML</h3>
<p>最終的に以下の HTML になります。</p>
<p><a href="https://gist.github.com/ytyng/26d183f6cb39eddb68e550985da6371a" target="_blank">完成した HTML のソースコード</a></p>
<h2 id="cssフレームワーク">CSSフレームワーク</h2>
<p>当社では、通常は CSS フレームワークの使用を推奨しています。 ページレイアウトを行う際は <a href="https://getbootstrap.com/" target="_blank">Bootstrap</a> や <a href="https://tailwindcss.com/" target="_blank">Tailwind</a> などのフレームワークが使われることが多いですが、今回は演習のため CSS フレームワークは使いません。</p>
<p>ただし、アイコンを表示するために <a href="https://icons.getbootstrap.com/" target="_blank">Bootstrap Icons</a> を使用します。</p>
<h2 id="そのほかのレイアウト方法との比較">そのほかのレイアウト方法との比較</h2>
<p>ページ全体をレイアウト(段組み)する方法として、HTML4の時代は <code>float</code> を使ったり、 <code>position: absolute</code> を使ったり、それ以前は <code>table</code> を使ったレイアウトもありました。 しかし、フレックスの登場で、それらを <strong>ページレイアウトで</strong> 使う必要は無くなりました。</p>
<p>フレックスを使うことで、あらゆるレイアウトを直感的に、少ない記述で美しくレイアウトができます。業務上必須となるため、慣れていない方は十分練習をしてください。</p>
<h3 id="グリッドとの比較">グリッドとの比較</h3>
<p>同時期に策定された <code>display: grid;</code> (<a href="https://developer.mozilla.org/ja/docs/Web/CSS/grid" target="_blank">グリッドレイアウト</a>)も、よく使われるレイアウト手法です。 サイト全体をグリッドでレイアウトすることもできますが、複数行x複数列のグリッドを作ると各要素の設定が疎にならないため、最初はいいのですが<strong>後から修正を行う際に複雑になりがち</strong>ですので、採用するにあたり十分考慮したほうが良いと思います。</p>
<p>今回の記事ではグリッドについては触れません。</p>
<h2 id="フレックスのレイアウトの基本的な考え方">フレックスのレイアウトの基本的な考え方</h2>
<p>フレックスは、内容を縦横自由に、柔軟に並べることができます。</p>
<p>そのため、まずは理想とするレイアウトをイメージし、その中で何がどの方向で並んでいるかを意識する必要があります。</p>
<p>まず、今回のレイアウトでは、まず一番大きく並んでいる要素は、ヘッダー、中央コンテンツ、フッターです。 そのため、それらのを含む親要素は、子要素を縦に並ばせるフレックスコンテナーとします。</p>
<p><img alt="" height="669" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/flex/blog-flex-11.png" width="959"/></p>
<p>ヘッダーの中を見ると、左上の地球儀のアイコン、サイト名、右上のお知らせや設定ボタンが横並びになっているので、ヘッダーは子要素を横に並ばせるフレックスコンテナーとし、</p>
<p><img alt="" height="228" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/flex/blog-flex-22.png" width="959"/></p>
<p>さらに、お知らせアイコンとその下のテキストは、縦並びになっているので子要素を縦に並ばせるフレックスコンテナーにしてレイアウトを作ります。</p>
<p><img alt="" height="173" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/flex/blog-flex-23.png" width="319"/></p>
<p>同じように、中央コンテンツは、ナビとメインコンテンツが横並びになっているので、子要素を横に並ばせるフレックスコンテナーを作ります。</p>
<p><img alt="" height="655" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/flex/blog-flex-24.png" width="959"/></p>
<p>フッターも、「サイトフッター」という文言と「連絡する」の文言が横にならんでいるため、子要素を横に並ばせるフレックスコンテナーにします。</p>
<p><img alt="" height="145" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/flex/blog-flex-25.png" width="959"/></p>
<h2 id="演習">演習</h2>
<h3 id="1-課題ファイルをコピーして開く">1. 課題HTMLをコピーして開く</h3>
<p><a href="https://gist.github.com/ytyng/d83b840c334ac251816c038ac00e0280" target="_blank">課題HTML</a> をコピーし、Mac 内に <code>.html</code> のファイルとして保存してください。</p>
<p>保存後、ブラウザで開いてください。</p>
<p><img alt="" height="657" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/flex/blog-flex-21.png" width="959"/></p>
<h3 id="2-ヘッダーを作り込む">2. ヘッダーを作り込む</h3>
<p>まずは、ヘッダーから作り込んでいきます。</p>
<p>本来は、一番外側の要素 (<code>body</code>) から作り込んでいくのが良いと思いますが、 今回の課題は、フッターを下部に固定する箇所が、他の要素を作りこないと挙動がわかりにくいため、 一番外側の <code>body</code> のレイアウトは<strong>最後に行います</strong>。</p>
<p><code>header</code> の中には <code>.header-left</code> と <code>.header-right</code> の2つのエレメントが入っています。 これらを横並びにするため、 <code>header</code> に <code>display: flex;</code> を指定します。</p>
<pre><code class="language-css hljs"><span class="hljs-selector-tag">header</span> {
<span class="hljs-attribute">background-color</span>: royalblue;
<span class="hljs-attribute">color</span>: white;
<span class="hljs-attribute">display</span>: flex; <span class="hljs-comment">/* これを追加 */</span>
}
</code></pre>
<p>これを指定しただけだと、右側に無駄な余白ができてしまいます。</p>
<p><img alt="" height="249" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/flex/blog-flex-31.png" width="959"/></p>
<p>中央に余白を作るためには、 <code>header</code> に <code>justify-content: space-between;</code> を設定するか、 <code>.header-left</code> に <code>flex-grow: 1;</code> を設定します。</p>
<p>それぞれ意味が違いますが、今回はどちらでもOKです。今回は、<code>justify-content: space-between;</code> を使います。</p>
<pre><code class="language-css hljs"><span class="hljs-selector-tag">header</span> {
<span class="hljs-attribute">background-color</span>: royalblue;
<span class="hljs-attribute">color</span>: white;
<span class="hljs-attribute">display</span>: flex;
<span class="hljs-attribute">justify-content</span>: space-between;
}
</code></pre>
<p><img alt="" height="167" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/flex/blog-flex-32.png" width="959"/></p>
<p>ヘッダー内部を作り込みます。</p>
<p><code>.header-left</code>, <code>.header-right</code> もそれぞれフレックスコンテナーとして、さらに右上部のメニューアイテムもフレックスコンテナーとします。</p>
<p>フォントサイズやパディングを調整し、完成したヘッダー部分の CSS は以下のようになります。</p>
<pre><code class="language-css hljs"><span class="hljs-selector-tag">header</span> {
<span class="hljs-attribute">background-color</span>: royalblue;
<span class="hljs-attribute">color</span>: white;
<span class="hljs-attribute">display</span>: flex;
<span class="hljs-attribute">justify-content</span>: space-between;
<span class="hljs-selector-class">.header-left</span> {
<span class="hljs-attribute">padding</span>: <span class="hljs-number">0.5em</span>;
<span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.5em</span>;
}
<span class="hljs-selector-class">.header-right</span>{
<span class="hljs-attribute">display</span>: flex;
<span class="hljs-attribute">gap</span>: <span class="hljs-number">1em</span>;
<span class="hljs-attribute">justify-content</span>: center;
<span class="hljs-attribute">align-items</span>: center;
<span class="hljs-attribute">padding</span>: <span class="hljs-number">0.5em</span> <span class="hljs-number">1em</span>;
<span class="hljs-selector-class">.header-menu-item</span> {
<span class="hljs-attribute">display</span>: flex;
<span class="hljs-attribute">gap</span>: <span class="hljs-number">0.2em</span>;
<span class="hljs-attribute">align-items</span>: center;
<span class="hljs-attribute">flex-direction</span>: column;
<span class="hljs-selector-class">.header-menu-text</span> {
<span class="hljs-attribute">font-size</span>: <span class="hljs-number">0.5em</span>;
}
}
}
}
</code></pre>
<p><img alt="" height="198" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/flex/blog-flex-33.png" width="959"/></p>
<h3 id="3-中央コンテンツを作り込む">3. 中央コンテンツを作り込む</h3>
<p>中央コンテンツ の <code>.body-container</code> を作り込んでいきます。 これも内容を横に並ばせるフレックスコンテナーにするので、<code>display: flex;</code> を指定します。</p>
<pre><code class="language-css hljs"><span class="hljs-selector-class">.body-container</span> {
<span class="hljs-attribute">display</span>: flex; <span class="hljs-comment">/* 追加 */</span>
<span class="hljs-selector-tag">nav</span> {
<span class="hljs-attribute">background-color</span>: <span class="hljs-number">#ddd</span>;
}
<span class="hljs-selector-tag">main</span> {
}
}
</code></pre>
<p>そのままだと、<code>nav</code> の幅が狭いため、サイズを指定します。今回は、 <code>width: 20em;</code> を指定します。 <code>width: 300px;</code> 等の指定でも OKです。</p>
<pre><code class="language-css hljs"><span class="hljs-selector-class">.body-container</span> {
<span class="hljs-attribute">display</span>: flex;
<span class="hljs-selector-tag">nav</span> {
<span class="hljs-attribute">background-color</span>: <span class="hljs-number">#ddd</span>;
<span class="hljs-attribute">width</span>: <span class="hljs-number">20em</span>; <span class="hljs-comment">/* 追加 */</span>
}
<span class="hljs-selector-tag">main</span> {
}
}
</code></pre>
<p><code><img alt="" height="656" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/flex/blog-flex-35.png" width="908"/></code></p>
<p>一見大丈夫そうですが、このままでは少し問題があります。<code>main</code> の背景色と <code>html</code> の背景色が同じでわかりにくいのですが、現在 <code>main</code> の中身は、横幅が自動調整され、ページの右端まで使われていません。</p>
<p>試しに、 <code>main</code> に <code>background-color: yellow;</code> を指定してみた例が以下の画像です。</p>
<p><img alt="" height="658" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/flex/blog-flex-34.png" width="922"/></p>
<p>このままでは、コンテンツが横に長くなった場合に問題が発生しやすいため、 <code>main</code> は幅いっぱいまで使うようにします。</p>
<p>そのためには、 <code>main</code> に <code>flex-grow: 1;</code> を指定します。</p>
<p>ついでに適当に <code>padding</code> も設定し、 完成した <code>.body-container</code> の CSS は以下のようになります。</p>
<pre><code class="language-css hljs"><span class="hljs-selector-class">.body-container</span> {
<span class="hljs-attribute">display</span>: flex; <span class="hljs-comment">/* 追加 */</span>
<span class="hljs-selector-tag">nav</span> {
<span class="hljs-attribute">background-color</span>: <span class="hljs-number">#ddd</span>;
<span class="hljs-attribute">width</span>: <span class="hljs-number">20em</span>;
<span class="hljs-attribute">padding</span>: <span class="hljs-number">1em</span>;
}
<span class="hljs-selector-tag">main</span> {
<span class="hljs-attribute">flex-grow</span>: <span class="hljs-number">1</span>; <span class="hljs-comment">/* 追加 */</span>
<span class="hljs-attribute">padding</span>: <span class="hljs-number">1em</span>;
}
}
</code></pre>
<p>この状態では、まだ <code>main</code> の内容がスクロールするボックスの中に収まっていませんが、一旦次へ進みます。</p>
<h3 id="3-フッターを作り込む">3. フッターを作り込む</h3>
<p>要素を横に並べるため、<code>footer</code> にも <code>display: flex;</code> を設定し、中央に余白を入れるため <code>justify-content: space-between;</code> を設定します。</p>
<p><code>padding</code> 等を調整し、<code>footer</code> の CSS は以下のようになります。</p>
<pre><code class="language-css hljs"><span class="hljs-selector-tag">footer</span> {
<span class="hljs-attribute">background-color</span>: <span class="hljs-number">#bbb</span>;
<span class="hljs-attribute">font-size</span>: <span class="hljs-number">0.85em</span>;
<span class="hljs-attribute">display</span>: flex; <span class="hljs-comment">/* 追加 */</span>
<span class="hljs-attribute">justify-content</span>: space-between; <span class="hljs-comment">/* 追加 */</span>
<span class="hljs-selector-class">.footer-left</span> {
<span class="hljs-attribute">padding</span>: <span class="hljs-number">0.5em</span>;
}
<span class="hljs-selector-class">.footer-right</span> {
<span class="hljs-attribute">padding</span>: <span class="hljs-number">0.5em</span>;
}
}
</code></pre>
<p><img alt="" height="194" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/flex/blog-flex-36.png" width="911"/></p>
<h3 id="4-全体のレイアウトを作る">4. 全体のレイアウトを作る</h3>
<p>不足しているレイアウトを整えていきます。</p>
<ul>
<li><code>body</code> に <code>display: flex;</code> を指定。 <code>header</code>, <code>.body-container</code>, <code>footer</code> を一つのフレックスコンテナーにいれるため。</li>
<li><code>body</code> に <code>flex-direction: column;</code> を指定。子要素を縦に並べるため。</li>
<li><code>body</code> に <code>height: 100%;</code> を指定。<code>body</code> の高さを制限するため。</li>
<li><code>main</code> に <code>overflow-y: auto;</code> を指定。収まらない部分をスクロールさせるため。</li>
<li><code>.body-container</code> に <code>flex-grow: 1;</code> を指定。サイズを可変にするため。</li>
<li><code>.body-container</code> に <code>min-height: 0;</code> を指定。直感的にかなりわかりにくいですが、 この指定を行わない限り、main の天地サイズがうまく適用されません。</li>
</ul>
<p>完成した、 <code>body</code> と <code>.body-container</code> の CSS は以下のようになります。</p>
<pre><code class="language-css hljs"><span class="hljs-selector-tag">body</span> {
<span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
<span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span>;
<span class="hljs-attribute">display</span>: flex;
<span class="hljs-attribute">height</span>: <span class="hljs-number">100%</span>;
<span class="hljs-attribute">flex-direction</span>: column;
}
<span class="hljs-selector-class">.body-container</span> {
<span class="hljs-attribute">display</span>: flex;
<span class="hljs-attribute">flex-grow</span>: <span class="hljs-number">1</span>;
<span class="hljs-attribute">min-height</span>: <span class="hljs-number">0</span>; <span class="hljs-comment">/* なぜか必要 */</span>
<span class="hljs-selector-tag">nav</span> {
...
</code></pre>
<p>これで、期待通りの表示がされました。</p>
<p><img alt="" height="555" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/flex/blog-flex-37.png" width="959"/></p>
<p>最後に、HTML の全文を以下に掲載します。</p>
<p><a href="https://gist.github.com/ytyng/26d183f6cb39eddb68e550985da6371a" target="_blank">完成したHTML</a></p>[Django] python-social-auth + social-auth-app-django で、リダイレクトURLのスキーム(http/https)をコントロールする2024-02-26T11:05:01+00:002024-03-19T06:01:03+00:00四柳剛https://tech.torico-corp.com/blog/author/yotsuyanagi/https://tech.torico-corp.com/blog/python-social-auth-app-django-control-redirect-url-scheme/<p><a href="https://github.com/python-social-auth/social-app-django" target="_blank">python-social-auth + social-auth-app-django</a> を使ってログインシステムを構築している場合、OAuth プロバイダーにリダイレクトする際にクエリパラメーターに <code>redirect_uri</code> パラメーターを付与し、認証完了後にそのURLに戻ってきます。</p>
<p>その時、サーバーの構成によっては <code>redirect_uri</code> の URL のスキームが、本来 <code>https://</code> でなければならない所が <code>http://</code> になって困ることがあります。逆に PC で開発している時は、今度は <code>http://</code> になって欲しい箇所が <code>https://</code> になって困ることもあります。</p>
<p>それらのコントロールをする際、どのような設定をすればいいか、どこを見ればいいかを書いておきます。</p>
<h2>平文の URIに戻される理由</h2>
<p>サーバーシステムが全て <code>https://</code> なのに、 <code>redirect_uri</code> のスキームが平文の <code>http://</code> になる場合があります。</p>
<p>この原因は、システムの途中のリバースプロキシが https をデクリプトし、平文 http にしてバックエンドサーバーにリクエストするためです。</p>
<p>Django はそれを平文http のリクエストとして受け付けるため、平文 http リダイレクトURLを生成します。</p>
<p><a href="https://github.com/django/django/blob/main/django/core/handlers/wsgi.py#L82-L83" target="_blank">django.core.handlers.wsgi.WSGIRequest の _get_scheme</a> で行っています。</p>
<h2>対策</h2>
<h3>A. SECURE_PROXY_SSL_HEADER を使う</h3>
<p><code>settings</code> に</p>
<pre>SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')</pre>
<p>と書いておけば、リクエストヘッダーに <code>X-Forwarded-Proto: https</code> が含まれてた場合、リダイレクトのスキームを https:// にします。</p>
<p>使用箇所は、 <a href="https://github.com/django/django/blob/main/django/http/request.py#L254-L268" target="_blank">django/http/request.py の HttpRequest.scheme</a> プロパティを見てください。</p>
<p>注意点として、この設定の2項目目の '<code>https</code>' は、「<code>X-Forwarded-Proto </code>ヘッダーが無かった時のデフォルトのスキーム」<strong>ではありません</strong>。</p>
<p><code>X-Forwarded-Proto</code> ヘッダーが、この値と<strong>一致した時は <code>https</code></strong> になり、不一致の時は <code>http</code> になります。</p>
<p>そのため、もし</p>
<pre>SECURE_PROXY_SSL_HEADER = ('HTTP_X_REDIRECT_URL_IS_HTTPS', '1')</pre>
<p>という設定にした場合は、リクエストヘッダーが <code>X-Redirect-Url-Is-Https: 1</code> となっていた場合に <code>https</code> となり、それ以外は <code>http</code> となります。</p>
<h3>B. SOCIAL_AUTH_REDIRECT_IS_HTTPS を使う</h3>
<p><a href="https://github.com/python-social-auth/social-core/blob/master/social_core/strategy.py#L119-L123" target="_blank">social_core/strategy.py の BaseStrategy.absolute_uri()</a> を見ると、Django の <code>settings</code> に</p>
<pre>SOCIAL_AUTH_REDIRECT_IS_HTTPS = True</pre>
<p>もしくは</p>
<pre>REDIRECT_IS_HTTPS = True</pre>
<p>と書くと、ログイン時の <code>redirect_uri</code> のスキームを強制的に <code>https</code> に書き換えれることがわかります。</p>
<p><code>*_REDIRECT_IS_HTTPS</code> この prefix は、ライブラリの使い方によって変化しますのでご注意ください。</p>
<h3>C. 認証バックエンドの get_redirect_uri をオーバーライドする</h3>
<p><a href="https://github.com/python-social-auth/social-core/blob/master/social_core/backends/oauth.py#L107-L112" target="_blank">social.backends.oauth.BaseOAuth2</a> 等を継承して、独自の認証バックエンドを作って使っている場合は、<a href="https://github.com/python-social-auth/social-core/blob/master/social_core/backends/oauth.py#L107-L112" target="_blank">get_redirect_uri</a> メソッドをオーバーライドしても良いと思います。</p>
<pre>def get_redirect_uri(self, state=None):<br/> uri = super().get_redirect_uri(state)<br/> # ここで、条件によって URL スキームを文字列置換<br/> return uri</pre>
<h3>D. 独自 Strategy の absolute_uri をオーバーライドする</h3>
<p><a href="https://github.com/python-social-auth/social-core/blob/master/social_core/strategy.py#L119-L123" target="_blank">social_core.strategy.BaseStorategy</a> を継承した独自の Strategy を作って使っている場合は、 <a href="https://github.com/python-social-auth/social-core/blob/master/social_core/strategy.py#L119-L123" target="_blank">absolute_uri</a> メソッドをオーバーライドすることでURIスキームを自由にコントロールできます。</p>
<h3>E. django.core.handlers.wsgi.WSGIRequest の _get_scheme をパッチする</h3>
絶対URLを生成する際、リクエストのスキームの判定のため django.http.request.HttpRequest.scheme や、もしくはそこから <a href="https://github.com/django/django/blob/main/django/core/handlers/wsgi.py#L82-L83" target="_blank">django.core.handlers.wsgi.WSGIRequest の _get_scheme</a> がコールされます。
<p>少し強引ですがそこをパッチして、返すスキームをコントロールすることができます。</p>
<p>例えば、下記のようにパッチすれば、絶対URLを作る時はデフォルトで https スキームになります。</p>
<pre>from django.core.handlers.wsgi import WSGIRequest<br/><br/>def _get_scheme(self):<br/> return 'https'<br/><br/>WSGIRequest._get_scheme = _get_scheme</pre>
<p>また、下記のようにパッチをすれば settings の値で動作を変えられます。</p>
<pre>from django.core.handlers.wsgi import WSGIRequest<br/>from django.conf import settings<br/><br/>def _get_scheme(self):<br/> return getattr(settings, 'ABSOLUTE_URL_SCHEME', 'https')<br/><br/>WSGIRequest._get_scheme = _get_scheme</pre>
<p>この場合、social-auth-app-django + python-social-auth に限らず、広い範囲で絶対URL を作る際に適用されますのでご注意ください。</p>
<p>また、 <code>django.core.handlers.wsgi</code> は <code>django.core.settings</code> をインポートしていますので、 <code>django.core.settings</code> の<strong>評価後でないとパッチできません</strong>。メイン app の app.py や url.py の評価時にパッチすれば良いと思います。</p>kubernetes の cronjob のマニフェストファイルが長すぎ問題に対応するために動的に作る2023-12-30T05:08:22+00:002024-03-19T06:48:23+00:00四柳剛https://tech.torico-corp.com/blog/author/yotsuyanagi/https://tech.torico-corp.com/blog/kubernetes-cronjob-manifest-maked-dynamic/<p>kubernetes の cronjob のマニフェストファイルを書く時、普通に手作業で YAML ファイルを書こうとすると Cronjob ごとに同じ項目が多くなるため、DRYでなくなり管理がしにくくなります。 Pod内で複数のコンテナを扱ったり、volumeMount が多いワークロードを動かす時はより困難になります。</p>
<p>そのため、cronjob のマニフェストは手で書かず、半分動的に作る方法に落ち着きましたので紹介します。</p>
<p>テンプレートの YAMLファイルと差分を用意し、それを Python スクリプトで組み合わせて Cronjob の YAML を組み立てます。</p>
<h1 id="cronjob-のテンプレートファイルを作る">cronjob のテンプレートファイルを作る</h1>
<h2 id="cronjob-templateyaml">cronjob-template.yaml</h2>
<pre><code class="language-yaml hljs"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">batch/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">CronJob</span>
<span class="hljs-attr">metadata:</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">UNTITLED</span> <span class="hljs-comment"># override</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">torico</span>
<span class="hljs-attr">labels:</span>
<span class="hljs-attr">app:</span> <span class="hljs-string">my-awesome-app</span>
<span class="hljs-attr">spec:</span>
<span class="hljs-attr">concurrencyPolicy:</span> <span class="hljs-string">Replace</span>
<span class="hljs-attr">schedule:</span> <span class="hljs-string">"30 12 * * *"</span> <span class="hljs-comment"># override</span>
<span class="hljs-attr">jobTemplate:</span>
<span class="hljs-attr">spec:</span>
<span class="hljs-attr">template:</span>
<span class="hljs-attr">spec:</span>
<span class="hljs-attr">restartPolicy:</span> <span class="hljs-string">OnFailure</span>
<span class="hljs-attr">containers:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">command:</span> [] <span class="hljs-comment"># override</span>
<span class="hljs-attr">args:</span> [] <span class="hljs-comment"># override</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">my-awesome-app</span>
<span class="hljs-attr">image:</span> <span class="hljs-string">torico/my-awesome-app-image:latest</span>
<span class="hljs-attr">imagePullPolicy:</span> <span class="hljs-string">Always</span>
<span class="hljs-attr">env:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DJANGO_SETTINGS_MODULE</span>
<span class="hljs-attr">value:</span> <span class="hljs-string">my_awesome_app.settings.production</span>
<span class="hljs-attr">volumeMounts:</span> []
<span class="hljs-attr">volumes:</span> []
</code></pre>
<p>正しい YAML ファイルとして、cronjob のテンプレートを作ります。 volumeMounts, volumes は空リストにしていますが、これは deployment.yaml にある定義からコピーします。</p>
<p>image, env 等も deployment からコピーしてもいいと思いますし、containers をまるごと deployment からコピーして来ても良いと思います。</p>
<h1 id="deployments-のファイル">deployments のファイル</h1>
<p>deployments は既に運用しているものがあります。</p>
<pre><code class="language-yaml hljs"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">my-awesome-app-deployment</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">torico</span>
<span class="hljs-attr">spec:</span>
<span class="hljs-attr">replicas:</span> <span class="hljs-number">2</span>
<span class="hljs-attr">selector:</span>
<span class="hljs-attr">matchLabels:</span>
<span class="hljs-attr">app:</span> <span class="hljs-string">my-awesome-app</span>
<span class="hljs-attr">template:</span>
<span class="hljs-attr">metadata:</span>
<span class="hljs-attr">labels:</span>
<span class="hljs-attr">app:</span> <span class="hljs-string">my-awesome-app</span>
<span class="hljs-attr">spec:</span>
<span class="hljs-attr">containers:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">my-awesome-app</span>
<span class="hljs-attr">image:</span> <span class="hljs-string">torico/my-awesome-app-image:latest</span>
<span class="hljs-attr">imagePullPolicy:</span> <span class="hljs-string">Always</span>
<span class="hljs-attr">env:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DJANGO_SETTINGS_MODULE</span>
<span class="hljs-attr">value:</span> <span class="hljs-string">my_awesome_app.settings.production</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">8002</span>
<span class="hljs-attr">volumeMounts:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">storage-1</span>
<span class="hljs-attr">mountPath:</span> <span class="hljs-string">/mnt/storage-1</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">storage-2</span>
<span class="hljs-attr">mountPath:</span> <span class="hljs-string">/mnt/storage-2</span>
<span class="hljs-attr">readinessProbe:</span>
<span class="hljs-attr">httpGet:</span>
<span class="hljs-attr">port:</span> <span class="hljs-number">8002</span>
<span class="hljs-attr">path:</span> <span class="hljs-string">/</span>
<span class="hljs-attr">livenessProbe:</span>
<span class="hljs-attr">httpGet:</span>
<span class="hljs-attr">port:</span> <span class="hljs-number">8002</span>
<span class="hljs-attr">path:</span> <span class="hljs-string">/</span>
<span class="hljs-attr">volumes:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">storage-1</span>
<span class="hljs-attr">persistentVolumeClaim:</span>
<span class="hljs-attr">claimName:</span> <span class="hljs-string">pvc-storage-1</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">storage-2</span>
<span class="hljs-attr">persistentVolumeClaim:</span>
<span class="hljs-attr">claimName:</span> <span class="hljs-string">pvc-storage-2</span>
</code></pre>
<p>実際は、volumeのマウントが10個ぐらいあるワークロードだったため、コピペで cronjob マニフェストを作るのは避けました。</p>
<h1 id="cronjob-の差分ファイルを作る">cronjob の差分ファイルを作る</h1>
<h2 id="cronjob-specspy">cronjob-specs.py</h2>
<pre><code class="language-yaml hljs">
<span class="hljs-string">LOG_DIR</span> <span class="hljs-string">=</span> <span class="hljs-string">'/var/log/cronlog'</span>
<span class="hljs-string">cronjob_specs</span> <span class="hljs-string">=</span> [
{
<span class="hljs-attr">'metadata.name':</span> <span class="hljs-string">'my-awesome-app-cronjob-1'</span>,
<span class="hljs-comment"># JST 2:10, 19:10</span>
<span class="hljs-attr">'spec.schedule':</span> <span class="hljs-string">'10 10,17 * * *'</span>,
<span class="hljs-attr">'spec.jobTemplate.spec.template.spec.containers.0.command':</span> [
<span class="hljs-string">'/bin/bash'</span>,
],
<span class="hljs-attr">'spec.jobTemplate.spec.template.spec.containers.0.args':</span> [
<span class="hljs-string">'-c'</span>,
<span class="hljs-string">f'/app/sh/app-job-1.sh</span> <span class="hljs-string">>></span> {<span class="hljs-string">LOG_DIR</span>}<span class="hljs-string">/app-job-1.log</span> <span class="hljs-number">2</span><span class="hljs-string">>&1'</span>,
],
},
{
<span class="hljs-attr">'metadata.name':</span> <span class="hljs-string">'my-awesome-app-cronjob-1'</span>,
<span class="hljs-comment"># JST 4:10, 14:10</span>
<span class="hljs-attr">'spec.schedule':</span> <span class="hljs-string">'10 5,19 * * *'</span>,
<span class="hljs-attr">'spec.jobTemplate.spec.template.spec.containers.0.command':</span> [
<span class="hljs-string">'/bin/bash'</span>,
],
<span class="hljs-attr">'spec.jobTemplate.spec.template.spec.containers.0.args':</span> [
<span class="hljs-string">'-c'</span>,
<span class="hljs-string">f'/app/sh/app-job-2.sh</span> <span class="hljs-string">>></span> {<span class="hljs-string">LOG_DIR</span>}<span class="hljs-string">/app-job-2.log</span> <span class="hljs-number">2</span><span class="hljs-string">>&1'</span>,
],
},
<span class="hljs-string">...</span>
]
</code></pre>
<p>ジョブの差分のみ、列挙します。</p>
<p>なお、この例ではワークロードの都合上シェルスクリプトの結果をログファイルにリダイレクトしていますが、 実際はファイルに書き出さずに標準出力に出したほうが都合が良いと思います。</p>
<h1 id="cronjob-ファイルを生成するスクリプトを書く">cronjob ファイルを生成するスクリプトを書く</h1>
<p>Python で書きました。</p>
<pre><code class="language-python hljs"><span class="hljs-comment">#!/usr/bin/env python3</span>
<span class="hljs-keyword">import</span> copy
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">import</span> yaml
<span class="hljs-keyword">from</span> cronjob_specs <span class="hljs-keyword">import</span> cronjob_specs
<span class="hljs-keyword">def</span> <span class="hljs-title function_">set_value</span>(<span class="hljs-params">y: <span class="hljs-built_in">dict</span>, k: <span class="hljs-built_in">str</span>, v: <span class="hljs-built_in">any</span></span>) -> <span class="hljs-literal">None</span>:
keys = k.split(<span class="hljs-string">'.'</span>)
<span class="hljs-keyword">for</span> key <span class="hljs-keyword">in</span> keys[:-<span class="hljs-number">1</span>]:
<span class="hljs-keyword">if</span> <span class="hljs-built_in">isinstance</span>(y, <span class="hljs-built_in">list</span>):
y = y[<span class="hljs-built_in">int</span>(key)]
<span class="hljs-keyword">else</span>:
y = y[key]
y[keys[-<span class="hljs-number">1</span>]] = v
<span class="hljs-keyword">def</span> <span class="hljs-title function_">main</span>():
os.chdir(os.path.dirname(__file__))
cj = yaml.load(<span class="hljs-built_in">open</span>(<span class="hljs-string">'cronjob-template.yml'</span>, <span class="hljs-string">'r'</span>), Loader=yaml.SafeLoader)
dep = yaml.load(<span class="hljs-built_in">open</span>(<span class="hljs-string">'deployment.yml'</span>, <span class="hljs-string">'r'</span>), Loader=yaml.SafeLoader)
cronjob_yamls = []
<span class="hljs-keyword">for</span> spec <span class="hljs-keyword">in</span> cronjob_specs:
y = copy.deepcopy(cj)
<span class="hljs-built_in">print</span>(<span class="hljs-string">'{} を生成中'</span>.<span class="hljs-built_in">format</span>(spec[<span class="hljs-string">'metadata.name'</span>]))
<span class="hljs-keyword">for</span> k, v <span class="hljs-keyword">in</span> spec.items():
set_value(y, k, v)
y[<span class="hljs-string">'spec'</span>][<span class="hljs-string">'jobTemplate'</span>][<span class="hljs-string">'spec'</span>][<span class="hljs-string">'template'</span>][<span class="hljs-string">'spec'</span>][<span class="hljs-string">'containers'</span>][<span class="hljs-number">0</span>][
<span class="hljs-string">'volumeMounts'</span>
] = dep[<span class="hljs-string">'spec'</span>][<span class="hljs-string">'template'</span>][<span class="hljs-string">'spec'</span>][<span class="hljs-string">'containers'</span>][<span class="hljs-number">0</span>][<span class="hljs-string">'volumeMounts'</span>]
y[<span class="hljs-string">'spec'</span>][<span class="hljs-string">'jobTemplate'</span>][<span class="hljs-string">'spec'</span>][<span class="hljs-string">'template'</span>][<span class="hljs-string">'spec'</span>][<span class="hljs-string">'volumes'</span>] = dep[
<span class="hljs-string">'spec'</span>
][<span class="hljs-string">'template'</span>][<span class="hljs-string">'spec'</span>][<span class="hljs-string">'volumes'</span>]
cronjob_yamls.append(y)
<span class="hljs-keyword">with</span> (<span class="hljs-built_in">open</span>(<span class="hljs-string">'_generated_cronjob.yml'</span>, <span class="hljs-string">'w'</span>)) <span class="hljs-keyword">as</span> f:
f.write(<span class="hljs-string">'# このファイルは、generate_cronjob.py により生成されています。\n'</span>)
f.write(<span class="hljs-string">'# 直接編集してはいけません。\n'</span>)
yaml.dump_all(cronjob_yamls, f)
<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
main()
</code></pre>
<h1 id="kubernetes-に-apply-する">Kubernetes に Apply する</h1>
<h2 id="apply-cronjobsh">apply-cronjob.sh</h2>
<pre><code class="hljs language-bash"><span class="hljs-meta">#!/usr/bin/env zsh</span>
<span class="hljs-built_in">cd</span> $(<span class="hljs-built_in">dirname</span> <span class="hljs-variable">$0</span>) || <span class="hljs-built_in">exit</span>
<span class="hljs-built_in">export</span> KUBECONFIG=<span class="hljs-variable">${HOME}</span>/.kube/config-my-cluster
./generate_cronjob.py
kubectl apply -f _generated_cronjob.yml
</code></pre>PCを初期化した時にやったこと -ssh鍵の移行とBrewfileについて-2023-12-27T09:49:05+00:002024-03-19T06:04:25+00:00百合川紗璃奈https://tech.torico-corp.com/blog/author/sarina.yurikawa/https://tech.torico-corp.com/blog/pc%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%97%E3%81%9F%E6%99%82%E3%81%AB%E3%82%84%E3%81%A3%E3%81%9F%E3%81%93%E3%81%A8-ssh%E9%8D%B5%E3%81%AE%E7%A7%BB%E8%A1%8C%E3%81%A8brewfile%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6-/<span><span><br/>PCを初期化したのでその時にやったことを書いていきます。<br/><br/></span></span><hr/><span><br/></span><span class="c-mrkdwn__br"></span><span class="c-mrkdwn__br"></span>
<h3><span>①ssh鍵の移行について</span></h3>
<span>ssh鍵が.sshにある程で話を進めていきます。<br/></span><span class="c-mrkdwn__br"></span><span><br/><strong>[元のPCで打ち込むコマンド]</strong></span><br/>
<pre>$ls .ssh</pre>
<pre>$cp -a .ssh Desktop/ssh</pre>
<span>これでデスクトップのsshに鍵が入る<br/><br/></span><span class="c-mrkdwn__br"></span><strong>[使うPCで打ち込むコマンド]</strong><span><br/>手始めにAirDropにて先ほど作った鍵を使うPCに入れる。</span><br/>
<pre>$ls .ssh<br/><span>$cp -a Desktop/ssh .ssh</span></pre>
↑先ほどと同じようにデスクトップのsshに鍵が入るようにする<br/><span><span><br/>ディレクトリを強制削除して<br/></span></span>
<pre>$rm -rf .ssh/公開鍵</pre>
秘密鍵から公開鍵を作成する<br/>
<pre>$ssh-keygen -y -f 秘密鍵 > 公開鍵</pre>
<span><span>これでパスフレーズなしでssh接続ができるようになる<br/></span></span>
<pre>$ssh-add</pre>
<span>この時パスワードをきかれるので入力する</span>
<h3><span>②アプリ管理について</span></h3>
<span>Brewfileを使っていきます。<br/></span><span class="c-mrkdwn__br"></span><span>Homebrewのインストールをする</span> <span>......の前にxcodeがインストールされていないと、Homebrew自体をインストールできないので、xcodeをインストールするためのコマンドを打ち込む。</span><span><span><br/></span></span>
<pre>$ xcode-select --install</pre>
<span>次にここにかいてあるコマンドをターミナルにコピペしてインストール<br/></span><span class="c-mrkdwn__br"></span><a class="c-link" href="https://brew.sh/" rel="noopener noreferrer" target="_blank">Homebrew<br/></a><span><br/></span><span class="c-mrkdwn__br"></span><span>しかしここで問題が起きる。<br/></span><span><br/>brewコマンドが使えると思ったら使えてない。</span><br/><span>pathが通ってないのでpathを通すためにコマンドを打ち込む。<br/></span><span><span><br/></span></span>
<pre>$export PATH="$PATH:/opt/homebrew/bin/"</pre>
<span>こうすると無事使えるようになる。<br/><br/></span><span class="c-mrkdwn__br"></span><span>このあとは早速、Brewfileを作成する。<br/></span><span class="c-mrkdwn__br"></span>
<pre>$brew bundle dump</pre>
<span><br/></span><span class="c-mrkdwn__br"></span><span>このコマンドで勝手にBrewfileが作成されるので、AirDropなどを使って、Brewfileを新しいPCに移動させる。<br/></span><span class="c-mrkdwn__br"></span>
<pre>$brew bundle</pre>
<span><br/></span><span>これでインストールすれば、以前使っていた環境のプログラムをインストールすることができます<br/><br/></span>プライベートでgitにリポジトリを作ってBrewfileの中身を保存しておけば、クラッシュが起きたときにすぐ復元できるので便利です。ラズパイにVNCを導入した②2023-12-26T03:37:01+00:002024-03-19T04:29:09+00:00百合川紗璃奈https://tech.torico-corp.com/blog/author/sarina.yurikawa/https://tech.torico-corp.com/blog/%E3%83%A9%E3%82%BA%E3%83%91%E3%82%A4%E3%81%ABvnc%E3%82%92%E5%B0%8E%E5%85%A5%E3%81%97%E3%81%9F%E2%91%A1/<br/><br/>ラズパイにVNCを導入した話の2弾になります。<br/><br/><br/><hr/><br/>前回のおさらい<br/>前回の記事はこちら→<a href="https://tech.torico-corp.com/blog/%E3%83%A9%E3%82%BA%E3%83%91%E3%82%A4%E3%81%ABVNC%E3%82%92%E5%B0%8E%E5%85%A5%E3%81%97%E3%81%9F%E2%91%A0/">ラズパイにVNCを導入した①<br/><br/></a>RealVNCを使ってみる<br/><br/>最初に前回の記事の共通事項を確認してvimのインストールまで終わらせます。<br/><br/>次にRealVNCはアプリケーションをインストールする必要があります。<br/>下記リンクから自分のOSに合わせてダウンロードして下さい。<br/><a href="https://www.realvnc.com/en/connect/download/viewer/">https://www.realvnc.com/en/connect/download/viewer/<br/><br/></a>ここからは手動操作になります。<br/><br/><strong>ラズパイを起動</strong>→<strong>左上ラズベリーマークをクリック</strong>→<strong>設定</strong>→<strong>Rasberry Piの設定</strong>→<strong>インターフェイス</strong>→<strong>VNCをオンにする<br/></strong><br/>これでVNCの起動ができました。<br/><br/>また以下の方法でもVNCの起動が可能です。<br/><br/>ラズパイの設定を開くコマンド<br/>
<pre>$ sudo raspi-config</pre>
これを打ち込めば設定画面が出てくるので<br/><strong>Interface option</strong>→<strong>VNC</strong>→<strong>Yes</strong>を選んでVNCを起動します。<br/><br/>またもっと簡単な方法として<br/>ターミナルで<br/><br/>
<div>
<pre>$ sudo raspi-config nonint do_vnc 0</pre>
<span><span><span><span><br/>上記を打ち込めばVNCをオンにすることができます。<br/><br/>これで先ほど入れたRealVNCを起動させ、<br/>IPアドレスで繋げます。<br/></span></span></span></span>
<h3>ansibleについて</h3>
<br/>主に.ymlファイルと.runファイルを使い、動かすだけでラズパイの設定ができるようにしています。<br/><br/><strong>01-authorizekey</strong><br/>SSH公開鍵を管理するためのansible<br/>これをすると、複数のユーザーがラズパイに接続できるようになる<br/><br/><em>playbook.yml</em>
<div>
<pre>---<br/>- <span>hosts</span>: servers<br/> <span>gather_facts</span>: no<br/> <span>tasks</span>:<br/> - <span>authorized_key</span>:<br/> <span>user</span>: <span>"{{ ansible_ssh_user }}"<br/></span><span> </span><span>key</span>: <span>"{{ lookup('file', 'present-keys.pub') }}"<br/></span><span> </span><span>state</span>: present<br/> - <span>authorized_key</span>:<br/> <span>user</span>: <span>"{{ ansible_ssh_user }}"<br/></span><span> </span><span>key</span>: <span>"{{ lookup('file', 'absent-keys.pub') }}"<br/></span><span> </span><span>state</span>: absent<br/><br/></pre>
</div>
</div>
<span>present-keys.pub,<span>absent-keys.pubを作成し、その中に公開鍵を入れる。<br/>presentkeyは追加する鍵、absentkeyは削除する鍵<br/><br/><strong>02-crontab</strong><br/></span></span>毎月1日の午前6時にシステムの更新と再起動を行う。<br/><em><br/>crontab.yml</em><br/>
<div>
<pre>---<br/>- <span>hosts</span>: servers<br/> <span>tasks</span>:<br/> - <span>copy</span>:<br/> <span>src</span>: crontab<br/> <span>dest</span>: <span>"{{ ansible_env.HOME}}/.crontab"<br/></span><span> </span><span>owner</span>: <span>"{{ ansible_ssh_user }}"<br/></span><span> </span><span>group</span>: <span>"{{ ansible_ssh_user }}"<br/></span><span> </span><span>mode</span>: 0644<br/><br/> - <span>shell</span>: crontab {{ ansible_env.HOME}}/.crontab</pre>
</div>
<br/><em>crontab</em><br/>
<div>
<pre>0 6 1 * * sudo bash -c "apt update -y && apt upgrade -y && apt autoremove -y && reboot now" >> ${HOME}/cronlog/system-update.log 2>&1</pre>
<br/><strong>03-install-package</strong><br/>パッケージ一覧の更新、パッケージの更新、vimのインストール、不要になったパッケージの削除を行います。<br/><em><br/>install-package.yml</em><br/>
<div>
<pre>- <span>hosts</span>: servers<br/> <span>remote_user</span>: pi<br/> <span>become</span>: True<br/> <span>tasks</span>:<br/><br/> - <span>name</span>: apt update<br/> <span>apt</span>:<br/> <span>update_cache</span>: yes<br/><br/> - <span>name</span>: apt upgrade<br/> <span>apt</span>:<br/> <span>upgrade</span>: yes<br/><br/> - <span>name</span>: apt install vim<br/> <span>apt</span>:<br/> <span>name</span>: vim<br/> <span>state</span>: present<br/><br/> - <span>name</span>: apt autoremove<br/> <span>apt</span>:<br/> <span>state</span>: absent<br/> <span>purge</span>: yes<br/> <span>autoremove</span>: yes</pre>
<br/>これであとはsshに接続し、VNCの設定をするために以下のようなコマンドを打ち込みます。<br/><br/>VNCをオンにする</div>
<pre>sudo raspi-config nonint do_vnc<span> </span><span><span><span>0</span></span></span></pre>
<br/>VNCの解像度を1280×1024にする</div>
<div>
<pre><span>sudo </span>raspi-config nonint do_vnc_resolution 1280x1024</pre>
<br/>これで簡単に複数のユーザーがVNCを使えるようになりました。<br/><br/><br/>[やってみてわかったこと]<br/><br/>上記のsudoコマンドをansible化する予定だったが、ansibleの仕様上なのか、うまくいかなかった。<br/>(非対話型なのが関係している?)<br/>今回の検証でラズパイを3回使えなくしました。。TT けど勉強になったし楽しかったです!<br/><br/></div>ラズパイにVNCを導入した①2023-12-22T15:00:00+00:002024-03-19T03:56:27+00:00百合川紗璃奈https://tech.torico-corp.com/blog/author/sarina.yurikawa/https://tech.torico-corp.com/blog/%E3%83%A9%E3%82%BA%E3%83%91%E3%82%A4%E3%81%ABVNC%E3%82%92%E5%B0%8E%E5%85%A5%E3%81%97%E3%81%9F%E2%91%A0/最近、ラズパイにVNCを導入するために色々試したのでその話をします。<br/><br/>まず結論として、色々試してみて使用を確認できたものは、<br/>デフォルトのRealVNC、TightVNCの2つでした。<br/>x11vncも使ってみたのですが起動がうまくいかず、、調べたところBookwormとの相性が悪いらしいのです。<br/>私の力では手に負えなかったので、もしできた方がいれば是非教えていただきたいです。<br/><br/>当記事では①TightVNCについて、②RealVNCとansible化について<br/>二日間で分けて書いていこうと思います。<br/>アドベントカレンダーが始まっているのでそちらも是非見てみて下さい。<br/><a class="c-link" href="https://qiita.com/advent-calendar/2023/torico" rel="noopener noreferrer" target="_blank">TORICO アドベントカレンダー2023</a><br/><br/>ちなみに今回使ったものはこちら↓↓↓<br/>macOS 13.4<br/>Raspberry Pi 4<br/>根気<br/><br/><hr/>
<h2>共通事項</h2>
<h4>ラズパイの初期設定</h4>
<br/> ①公式サイトにてデスクトップアプリをダウンロード<strong><br/></strong><a href="https://www.raspberrypi.com/software/">https://www.raspberrypi.com/software/<br/><br/></a>②デバイスを選択→<span><span><strong>Raspberry Pi 4<br/></strong>OSを選択→<strong>64bit</strong>(この時はリリース日時:2023-12-05のものを使用)<br/>ストレージは表示されているものを選択<br/><br/>③cmd + shift + xキーより設定項目を出す。<br/>これは設定しておくと後々便利になります。<br/><strong>オプション</strong>→<strong>サービス</strong>→<strong>SSHを有効化するにチェックマーク</strong>を入れ、<br/><strong>公開鍵認証のみを許可する</strong>を選択のち、自分の公開鍵を入れます。<br/><br/>これで設定は完了です。<br/>あとはラズパイ側で勝手にmicroSDに書き込んでくれます。<br/><br/>終わったらmicroSDをラズパイに差し込み、設定していきます。<br/></span></span>ラズパイのターミナルを使うか、sshの中に入って作業するかは各自で任せます。<br/>mac側で操作する場合はターミナルを開き、下記のコマンドを打ち込んでください。<br/>
<pre>$ <a href="mailto:ssh@%E3%83%9B%E3%82%B9">ssh ユーザー名@</a>ホスト名</pre>
<br/>パッケージの更新、vimのインストール<br/>
<pre>$ sudo apt update</pre>
<pre>$ sudo apt upgrade</pre>
<pre>$ sudo apt autoremove</pre>
<pre>$ sudo vim install</pre>
終わったら、早速TightVNCの設定に入っていきましょう。
<h3>TightVNCを使ってみる<br/><br/></h3>
<span><span><span>下記コマンドでインストール後、起動<br/>起動の際、パスワードの設定を求められるので設定します<br/></span></span></span>
<pre class="prism off-numbers language-plain"><code class="language-plain">$ sudo apt install tightvncserver<br/></code></pre>
<pre>$ tightvncserver</pre>
<h3>自動起動の設定</h3>
<br/>init.dにスクリプトを書いて自動起動するように設定します。
<pre class="prism off-numbers language-plain"><code class="language-plain">$ sudo vim /etc/init.d/vncboot<br/></code></pre>
<pre class="prism line-numbers language-bash"><code class="language-bash"><span class="token shebang important">#! /bin/sh</span>
<span class="token comment">### BEGIN INIT INFO</span>
<span class="token comment"># Provides: vncboot</span>
<span class="token comment"># Required-Start: $remote_fs $syslog</span>
<span class="token comment"># Required-Stop: $remote_fs $syslog</span>
<span class="token comment"># Default-Start: 2 3 4 5</span>
<span class="token comment"># Default-Stop: 0 1 6</span>
<span class="token comment"># Short-Description: Start VNC Server at boot time</span>
<span class="token comment"># Description: Start VNC Server at boot time.</span>
<span class="token comment">### END INIT INFO</span>
<span class="token comment"># /etc/init.d/vncboot</span>
USER<span class="token operator">=root</span>
HOME<span class="token operator">=</span>/root
<span class="token function">export</span> USER HOME
<span class="token keyword">case</span> <span class="token string">"<span class="token variable">$1</span>"</span> <span class="token keyword">in</span>
start<span class="token punctuation">)</span>
<span class="token keyword">echo</span> <span class="token string">"Starting VNC Server"</span>
<span class="token comment">#Insert your favoured settings for a VNC session</span>
<span class="token function">su</span> <span class="token variable">$USER</span> -c <span class="token string">'/usr/bin/vncserver :1 -geometry 1280x1024 -depth 24'</span>
<span class="token punctuation">;</span><span class="token punctuation">;</span>
stop<span class="token punctuation">)</span>
<span class="token keyword">echo</span> <span class="token string">"Stopping VNC Server"</span>
<span class="token function">su</span> <span class="token variable">$USER</span> -c <span class="token string">'/usr/bin/vncserver -kill :1'</span>
<span class="token punctuation">;</span><span class="token punctuation">;</span>
*<span class="token punctuation">)</span>
<span class="token keyword">echo</span> <span class="token string">"Usage: /etc/init.d/vncboot {start|stop}"</span>
<span class="token keyword">exit</span> 1
<span class="token punctuation">;</span><span class="token punctuation">;</span>
esac
<span class="token keyword">exit</span> 0</code></pre>
解像度は1280×1024で設定しています。ここは自由に変えて大丈夫です。<br/><br/>パーミッションの変更をして、自動起動の設定をします<br/>
<pre class="prism off-numbers language-plain"><code class="language-plain">$ sudo chmod 755 /etc/init.d/vncboot<br/></code></pre>
<pre class="prism off-numbers language-plain"><code class="language-plain">$ sudo update-rc.d vncboot defaults<br/></code></pre>
<br/>再起動<br/>
<pre class="prism off-numbers language-plain"><code class="language-plain">$ sudo shutdown -r now<br/></code></pre>
<br/>LISTENしてるか確認<br/>
<pre class="prism off-numbers language-plain"><code class="language-plain">$ netstat -nlt</code></pre>
状態が<strong>LISTENになっていれば自動起動の設定はOK</strong>です!
<h3>接続してみる</h3>
<br/> ラズパイ側の設定を終えたらmacで早速接続してみましょう。<br/><br/><手順><br/> Finderを開く→画面上左に出てくる移動を選択→サーバーに接続→アドレスを入力する<br/><br/>アドレスの書き方は以下の通り<br/>
<pre class="prism off-numbers language-plain"><code class="language-plain">vnc://ラズパイのIPアドレス:5901</code></pre>
5091はTightVNCのデフォルトのポート番号になります。<br/>ラズパイ左上のWi-Fiマークにカーソルを合わせると、IPアドレスが表示されるのでそこで確認してください。TORICOの自動テストの解説2023-12-17T12:05:25+00:002024-03-19T05:25:37+00:00四柳剛https://tech.torico-corp.com/blog/author/yotsuyanagi/https://tech.torico-corp.com/blog/some-test-codes-in-torico/<p>当社TORICOのサービスにおける、自動テストの構成・取り組みを紹介します。</p>
<h2 id="自動テストの実行環境">自動テストの実行環境</h2>
<p>当社では、社内サーバーに構築した <a href="https://www.jenkins.io/" target="_blank">Jenkins</a> での実行が多くを占めています。</p>
<p>Jenkins 以外では、Github actions, Magic Pod, Travis CI なども少量ありますが、多くは Jenkins です。 社内サーバーで起動しているため、コストやリソースの制限を気にせずにテストメソッドを起動できます。</p>
<p>Jenkins には、ユニットテストやE2E テストが50個ほど登録されており、コードのプッシュ時、もしくはタイマーで自動的に、テストを実行しています。</p>
<p>実行プログラムのビルドを行う場合はあまりなく、「テストコードを実行し、失敗した場合に通知する」という使い方が主となります。</p>
<p>自動テストのコードは、作っても自動実行させる環境が無いと有効に機能しません。 アプリ全体の自動テストの実行が手動での実行しか方法が無い場合、失敗した時の実行者のストレスが多くどこで失敗したのかの追跡も難しいため、常に自動的に実行し続ける環境の整備が必要です。</p>
<h3 id="jenkins内でのテストの起動方法">Jenkins内でのテストの起動方法</h3>
<p>各サービスのソースコードを Github からプルし、同時に各サービスの検証環境のDockerイメージを AWS ECR からプルし、ソースコードを Docker イメージにマウントさせて自動テストを行っています。すべて Docker で実行します。</p>
<p>Jenkins 自体も Kubernetes 上で Pod として起動していますが、ホストノードの Docker のドメインソケットをマウントしており、ホストノードの docker 実行環境をそのまま操作する形となっています。</p>
<h3 id="jenkins-の-kubernetes-マニフェスト">Jenkins の Kubernetes マニフェスト</h3>
<p>Jenkins は Kubernetes 上で起動しています。参考までに、文末に Dockerfile と Kubernetes のマニフェストファイルを参考までに書きます。</p>
<p>作ったのは少し古く、今はもう公開されていないベースイメージを使っていたため、イメージのタグ等は伏せたものとなります。</p>
<p><a href="https://tech.torico-corp.com/blog/feeds/atom/#jenkins-dockerfile">Dockerfile</a>, <a href="https://tech.torico-corp.com/blog/feeds/atom/#jenkins-kubernetes">Kubernetes</a> マニフェスト</p>
<h2 id="ユニットテスト">ユニットテスト</h2>
<p>ユニットテストには、書いたコードが仕様通り動いているかの動作保証をする他に、依存関係や実行環境のアップデートがあった場合、変わらずに動作し続けるかを保証することができます。</p>
<p>現在は10年前と比べて、苛烈とも言える勢いで依存環境がアップデートされていきます。乗り遅れずに、事故を少なくアップデートしていくにあたり、ユニットテストは必須です。</p>
<h3 id="django-のユニットテスト">Django のユニットテスト</h3>
<p>当社のサービスはウェブフレームーワークとして Django を多く使っています。 Django にはテスト環境がビルトインされていますので、それを使います。</p>
<p><a href="https://docs.djangoproject.com/en/5.0/topics/testing/overview/" target="_blank">Writing and running tests | Django documentation | Django</a></p>
<h4 id="使用ライブラリ">使用ライブラリ</h4>
<p>ほぼすべてのサービスの開発環境に含まれるライブラリを紹介します。</p>
<h5 id="freezegun">freezegun</h5>
<p>時間に関わるテストの際に使います。</p>
<p><a href="https://github.com/spulec/freezegun" target="_blank">https://github.com/spulec/freezegun</a></p>
<h5 id="factory-boy--faker">factory-boy + Faker</h5>
<p><a href="https://factoryboy.readthedocs.io/en/stable/" target="_blank">https://factoryboy.readthedocs.io/en/stable/</a></p>
<p>検証用のモデルデータの作成が非常に便利になります。</p>
<p>Django のモデルを作った際は、対応する DjangoModelFactory を作り、テストの実行を容易にします。</p>
<h5 id="tenacity">tenacity</h5>
<p><a href="https://github.com/jd/tenacity" target="_blank">https://github.com/jd/tenacity</a></p>
<p>リトライに使います。</p>
<p>ユニットテストという範疇からは超えるかもしれませんが、テストコード内で外部との通信を行う際はリトライを考慮する必要があるため、 tenacity を使ってリトライを行います。</p>
<p>テストコードで行うか、内部のロジックで行う必要があるのかは場合によると思いますが、まれに発生する通信障害で自動テストが失敗しアラートを出さないためにリトライを行うようにします。</p>
<h4 id="カバレッジ計測">カバレッジ計測</h4>
<p>現在、多くのプロダクトでカバレッジの計測はしていません。</p>
<h4 id="データベースエンジンにオンメモリの-sqlite3-を使う">データベースエンジンにオンメモリの SQLite3 を使う</h4>
<p>Django のユニットテストは通常、テスト関数の実行時にデータベースのスキーマを作成し、DDLを実行(スキーママイグレーション)し、終了時に消します。 そのまま使うと、マイグレーションファイルの量だけ時間がかかってしまい、非効率ですので、順次のマイグレーションを行わず、かつデータベースエンジンはオンメモリの SQLite を使うようにしています。</p>
<p>Setting では下記のようになります。</p>
<h4>myapp/settings/unittest.py</h4>
<pre><code class="language-python hljs">
<span class="hljs-keyword">from</span> .base <span class="hljs-keyword">import</span> * <span class="hljs-comment"># NOQA</span>
<span class="hljs-keyword">class</span> <span class="hljs-title class_">DisableMigrations</span>(<span class="hljs-title class_ inherited__">object</span>):
<span class="hljs-keyword">def</span> <span class="hljs-title function_">__contains__</span>(<span class="hljs-params">self, item</span>):
<span class="hljs-keyword">return</span> <span class="hljs-literal">True</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">__getitem__</span>(<span class="hljs-params">self, item</span>):
<span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>
DATABASES = {
<span class="hljs-string">'default'</span>: {
<span class="hljs-string">'ENGINE'</span>: <span class="hljs-string">'django.db.backends.sqlite3'</span>,
<span class="hljs-string">'NAME'</span>: <span class="hljs-string">':memory:'</span>,
},
}
MIGRATION_MODULES = DisableMigrations()
DATABASE_ROUTERS = []
</code></pre>
<h4 id="生sql-の-sqlite3-でのユニットテスト">生SQL の SQLite3 でのユニットテスト</h4>
<p>サービス内では、まれに生SQL を実行する箇所があります。</p>
<p>本番サービスのデータベースエンジンは SQLite3 ではなく MySQL のため、生SQL を SQLite3 で実行する時にエラーが発生することがあります。</p>
<p>簡易的な文字列置換で MySQL から SQLite3 への変換が行える場合は、変換をして SQLite3 でユニットテストを実行させます。 変換コードを付録に記載します。</p>
<p><a href="https://tech.torico-corp.com/blog/feeds/atom/#unittest-code">SQL を MySQL から SQLite3 に変換するコード</a></p>
<h3 id="python-の-doctest">Python の doctest</h3>
<p>Python にはコード内のドキュメンテーションにテストコードを書く機能「doctest」があります。</p>
<p><a href="https://docs.python.org/ja/3/library/doctest.html" target="_blank">doctest --- 対話的な実行例をテストする — Python 3.12.1 ドキュメント</a></p>
<p>シンプルな割に非常に便利で強力で、Python のキラー機能の一つといえます。私は大好きです。</p>
<p>関数やクラスのドキュメントに doctest が書いてあると、コードの使い方もすぐわかりますし、 その doctest が常に Jenkins で実行され、結果にエラーが無いことが保証されるため、安心感もあります。</p>
<p>そして、エディターによっては右クリックから一発でテストを実行できるようになっていますので、開発時も速度・精度の向上に繋がります。</p>
<p>ただし、DBを使うようなステートフルな処理に doctest を使うのは不向きです。</p>
<p>コンパクトでステートレスで、かつ読みにくい処理に使うと最適です。</p>
<p>例えば、正規表現で文字列をマッチさせるような関数や、文字コードやマルチバイト文字に関する関数、暗号化・復号化処理には doctest が必須といえます。</p>
<pre><code class="language-python hljs"><span class="hljs-keyword">def</span> <span class="hljs-title function_">calc_transition_timing_count</span>(<span class="hljs-params">
transition_duration_ms: <span class="hljs-built_in">int</span>,
transition_fps: <span class="hljs-built_in">int</span>
</span>) -> <span class="hljs-built_in">int</span>:
<span class="hljs-string">"""
transition_duration_ms, transition_fps を元に、
transition_timing_count を求める
>>> calc_transition_timing_count(2000, 10)
20
>>> calc_transition_timing_count(500, 10)
5
>>> calc_transition_timing_count(1000, 20)
20
"""</span>
<span class="hljs-keyword">return</span> <span class="hljs-built_in">int</span>(transition_fps * (transition_duration_ms / <span class="hljs-number">1000.0</span>))
</code></pre>
<pre><code class="language-python hljs">re_jp_phonenumbers = [
re.<span class="hljs-built_in">compile</span>(<span class="hljs-string">r'^(0\d{2})[- ](\d{4})[- ](\d{4})$'</span>),
re.<span class="hljs-built_in">compile</span>(<span class="hljs-string">r'^(0\d{1,5}?)[- ](\d{1,4})[- ](\d{4})$'</span>),
re.<span class="hljs-built_in">compile</span>(<span class="hljs-string">r'^(0\d{1,2}?)(\d{4})(\d{4})$'</span>),
]
<span class="hljs-keyword">def</span> <span class="hljs-title function_">format_jp_phonenumber</span>(<span class="hljs-params">value: <span class="hljs-built_in">str</span></span>) -> <span class="hljs-built_in">str</span> | <span class="hljs-literal">None</span>:
<span class="hljs-string">"""
>>> v = format_jp_phonenumber
>>> v('080-0000-1111')
'080-0000-1111'
>>> v('180-0000-1111')
>>> v('080-0000')
>>> v('08000001111')
'080-0000-1111'
>>> v('080 0000 1111')
'080-0000-1111'
>>> v('08O 0000 1111')
>>> v('0300001111')
'03-0000-1111'
>>> v('0250001111')
'02-5000-1111'
"""</span>
<span class="hljs-keyword">for</span> rpn <span class="hljs-keyword">in</span> re_jp_phonenumbers:
<span class="hljs-keyword">if</span> r := rpn.<span class="hljs-keyword">match</span>(value)
<span class="hljs-keyword">return</span> <span class="hljs-string">'{}-{}-{}'</span>.<span class="hljs-built_in">format</span>(r.group(<span class="hljs-number">1</span>), r.group(<span class="hljs-number">2</span>), r.group(<span class="hljs-number">3</span>))
</code></pre>
<h3 id="python-の-unittest">Python の Unittest</h3>
<p>Django に依存しないライブラリを開発する際は、Python の Unittest フレームワークを使ったユニットテストを書きます。</p>
<p><a href="https://docs.python.org/ja/3/library/unittest.html" target="_blank">unittest --- ユニットテストフレームワーク — Python 3.12.1 ドキュメント</a></p>
<p>当社では、API通信を行うクライアントライブラリが多くあります。API通信はモックを使ってテストを書く場合もありますが、モックを使わず実際に対向環境と通信をするユニットテストも多いです。 実際に通信を行うユニットテストが整備されていれば、通信先の破壊的アップデートによるトラブル時の原因の究明が素早く行えますので便利です。</p>
<h2 id="e2e-テスト">E2E テスト</h2>
<p>E2E (エンドツーエンド) テストは、ブラウザを操作してアプリの一連の動作を検証するテストです。</p>
<p>当社では、フロントンドフレームワークのユニットテストはあまり充実していませんが、システムの一連の動作を検証する E2E テストは整備しており、プログラムの反映時もしくは定期的に実行しています。</p>
<h3 id="テストクライアント">テストクライアント</h3>
<h4 id="robobrowserdash">RobobrowserDash</h4>
<p>JavaScript を考慮しなくてよい環境であれば、ウェブブラウザは起動せずに単純な HTTP クライアントを使います。</p>
<p>Pythonでよく使われる HTTP クライアントである requests をラップし、HTMLパーサーの BeautifulSoup と組み合わせて使うソリューションに 「<a href="https://pypi.org/project/robobrowser/" target="_blank">Robobrowser</a> 」というものがあり、簡単なテストであれば十分に機能します。</p>
<p>ただし、Robobrowser は2015年のアップデート以降更新が止まっており、最近の依存関係セットの中では動かすことはできません。 そのため、Robobrowser をフォークして <a href="https://pypi.org/project/robobrowserdash/%20" target="_blank">Roborowserdash</a> というパッケージ名として PyPI に登録し、使っています。</p>
<p>JS を動かさないため、安定したテストが行なえます。</p>
<p>ウェブブラウザを起動するものよりリソース消費が少ないため、より攻撃的な検証に使うこともあります。</p>
<p>例えば、残り1つの商品を100人で同時に購入を行い、排他制御とエラー処理が適切に行われているかのテストをする場合は、Robobrowserdash を使って検証を行います。</p>
<h4 id="selenium">Selenium</h4>
<p>Selenium と Python のテストフレームワークを用いた E2E テストはいくつかあります。</p>
<p>ただ、Selenium は、E2Eテストとして使うことは今後はあまり無いと思っています。</p>
<p>まれに、 Selenium の Python ラッパーである Selene を使っているものもあります。</p>
<h4 id="puppeteer-pyppeteer">Puppeteer, Pyppeteer</h4>
<p>比較的最近は、Chrome を操作する node.js のライブラリの Puppetter やその Python 版の Pyppeteer を使ってE2Eテストが書かれています。</p>
<p>ブラウザからの E2E テストを安定して行うにあたり、HTML のエレメントに <code>data-annotate="xxx"</code> という E2E テスト専用の属性を設定し、ブラウザはそのエレメントに対して QuerySelector を行って自動操作するような形式としています。</p>
<h4 id="pyautogui">Pyautogui</h4>
<p>印刷時のダイアログ等、ブラウザウインドウ範囲外のものを自動操作する場合は、Puppeteer では行えないため、 pyautogui を使います。</p>
<p>関連記事: <a href="https://qiita.com/soichiro-h/items/6b0b383847cd06253560" target="_blank">pyautogui を使ってみた #Python - Qiita</a></p>
<h2 id="セキュリティーテスト">セキュリティーテスト</h2>
<p>製品に対してのセキュリティー面のテストは、開発エンジニアではなく専門の品質管理部門が行います。</p>
<h3 id="appscan">AppScan</h3>
<p>アプリケーションに対してのセキュリティーテストは、主に HCL Software の AppScan を使います。</p>
<p>基本的に自動的に脆弱性をスキャンするソフトウェアですが、APIのエンドポイントやパラメーターを指定して重点的な検証も行えます。</p>
<p><a href="https://www.hcl-software.com/jp/products/appscan/products/appscan-standard" target="_blank">HCL AppScan Dynamic Application Security Testing (DAST) - HCLSoftware</a></p>
<h3 id="owasp-zap">OWASP ZAP</h3>
<p><a href="https://www.zaproxy.org/" target="_blank">ZAP</a></p>
<p>AppScan と併用し、OWASP ZAP も使います。こちらも AppScan と同様に、自動操作型の脆弱性スキャナーです。</p>
<p>AppScan では使用可能ライセンス数に限りがあるため、ライセンスを持っていない領域では OWASP ZAP を使っています。</p>
<h3 id="手動テスト">手動テスト</h3>
<p>そのほか、セキュリティーのテストに関しては <a href="https://portswigger.net/burp" target="_blank">Burp suite</a> や <a href="https://www.postman.com/" target="_blank">Postman</a> を使った手動の検証が多くを占めますが、今回の記事の範囲外ではないため割愛します。</p>
<h2 id="付録">付録</h2>
<h3 id="jenkins-dockerfile">Jenkins の Dockerfile</h3>
<p>昔作ったイメージを今でも使っており、今はもう存在しないベースイメージなどありますのでイメージタグは伏せています。参考程度にご覧ください。</p>
<pre><code class="hljs language-bash">
FROM alpine:** as builder
RUN apk --no-cache add \
gcc \
make \
python3-dev \
musl-dev \
libxml2-dev \
libxslt-dev \
libffi-dev \
libressl-dev
RUN pip3 install docker-compose
FROM jenkins/jenkins:lts-alpine
USER root
COPY --from=builder /usr/lib/python**/site-packages/ /usr/lib/python**/site-packages/
COPY --from=builder /usr/bin/docker-compose /usr/bin/docker-compose
RUN apk --no-cache add \
python3
ENV DOCKER_VERSION **
RUN curl -fL -o docker.tgz <span class="hljs-string">"https://download.docker.com/linux/static/test/x86_64/docker-<span class="hljs-variable">$DOCKER_VERSION</span>.tgz"</span> && \
tar --strip-components=1 -xvzf docker.tgz -C /usr/bin
RUN apk --update add tzdata && \
<span class="hljs-built_in">cp</span> /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
apk del tzdata && \
<span class="hljs-built_in">rm</span> -rf /var/cache/apk/*
ENV JENKINS_REF /usr/share/jenkins/ref
<span class="hljs-comment"># install jenkins plugins</span>
COPY ./plugins.txt <span class="hljs-variable">$JENKINS_REF</span>/
RUN /usr/local/bin/install-plugins.sh < <span class="hljs-variable">$JENKINS_REF</span>/plugins.txt
<span class="hljs-comment"># add docker group</span>
RUN addgroup -S docker && adduser jenkins docker
VOLUME /var/jenkins
</code></pre>
<h3 id="jenkins-kubernetes">Kubernetes のマニフェスト</h3>
<h4>deployment.yaml</h4>
<pre><code class="language-yaml hljs"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">torico</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">jenkins</span>
<span class="hljs-attr">spec:</span>
<span class="hljs-attr">selector:</span>
<span class="hljs-attr">matchLabels:</span>
<span class="hljs-attr">app:</span> <span class="hljs-string">jenkins</span>
<span class="hljs-attr">strategy:</span>
<span class="hljs-attr">type:</span> <span class="hljs-string">Recreate</span>
<span class="hljs-attr">template:</span>
<span class="hljs-attr">metadata:</span>
<span class="hljs-attr">labels:</span>
<span class="hljs-attr">app:</span> <span class="hljs-string">jenkins</span>
<span class="hljs-attr">spec:</span>
<span class="hljs-attr">containers:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">image:</span> **<span class="hljs-string">/torico/jenkins:latest</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">jenkins</span>
<span class="hljs-attr">env:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">JAVA_OPTS</span>
<span class="hljs-attr">value:</span> <span class="hljs-string">"-Duser.timezone=Asia/Tokyo"</span>
<span class="hljs-attr">ports:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">8080</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">http-port</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">50000</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">api-port</span>
<span class="hljs-attr">volumeMounts:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">home</span>
<span class="hljs-attr">mountPath:</span> <span class="hljs-string">/var/jenkins_home</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">ssh-keys</span>
<span class="hljs-attr">mountPath:</span> <span class="hljs-string">/root/.ssh</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">docker-socket</span>
<span class="hljs-attr">mountPath:</span> <span class="hljs-string">/var/run/docker.sock</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">docker-credentials</span>
<span class="hljs-attr">mountPath:</span> <span class="hljs-string">/root/.docker</span>
<span class="hljs-attr">imagePullSecrets:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">ecr-credeintial</span>
<span class="hljs-attr">volumes:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">home</span>
<span class="hljs-attr">hostPath:</span>
<span class="hljs-attr">path:</span> <span class="hljs-string">/data/jenkins</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">ssh-keys</span>
<span class="hljs-attr">hostPath:</span>
<span class="hljs-attr">path:</span> <span class="hljs-string">/data/jenkins/ssh</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">docker-socket</span>
<span class="hljs-attr">hostPath:</span>
<span class="hljs-attr">path:</span> <span class="hljs-string">/var/run/docker.sock</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">docker-credentials</span>
<span class="hljs-attr">hostPath:</span>
<span class="hljs-attr">path:</span> <span class="hljs-string">/home/ubuntu/.docker</span>
</code></pre>
<h4>service.yaml</h4>
<pre><code class="hljs language-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Service</span>
<span class="hljs-attr">metadata:</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">jenkins-service</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">torico</span>
<span class="hljs-attr">spec:</span>
<span class="hljs-attr">type:</span> <span class="hljs-string">NodePort</span>
<span class="hljs-attr">ports:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">port:</span> <span class="hljs-number">8080</span>
<span class="hljs-attr">protocol:</span> <span class="hljs-string">TCP</span>
<span class="hljs-attr">targetPort:</span> <span class="hljs-number">8080</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">jenkins-http</span>
<span class="hljs-attr">selector:</span>
<span class="hljs-attr">app:</span> <span class="hljs-string">jenkins</span>
</code></pre>
<h4>ingress.yaml</h4>
<pre><code class="hljs language-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">networking.k8s.io/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Ingress</span>
<span class="hljs-attr">metadata:</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">jenkins-ingress</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">torico</span>
<span class="hljs-attr">spec:</span>
<span class="hljs-attr">tls:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">hosts:</span>
<span class="hljs-bullet">-</span> <span class="hljs-string">jenkins.torico-tokyo.com</span>
<span class="hljs-attr">secretName:</span> <span class="hljs-string">tls-certificate</span>
<span class="hljs-attr">rules:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">host:</span> <span class="hljs-string">jenkins.torico-tokyo.com</span>
<span class="hljs-attr">http:</span>
<span class="hljs-attr">paths:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">path:</span> <span class="hljs-string">/</span>
<span class="hljs-attr">pathType:</span> <span class="hljs-string">Prefix</span>
<span class="hljs-attr">backend:</span>
<span class="hljs-attr">service:</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">jenkins-service</span>
<span class="hljs-attr">port:</span>
<span class="hljs-attr">number:</span> <span class="hljs-number">8080</span>
</code></pre>
<h3 id="unittest-code">ユニットテスト時、MySQL の生 SQL を SQLite3 へ変換するコード</h3>
<pre><code class="hljs language-sql">def db_engine_is_mysql():
"""
DBエンジンが MySQLか
通常、エンジンは MySQL だが、ユニットテストで SQLite3 を使うことがある。
SQLite3 の場合、特定の raw_query は動作しないので、それを回避するためのコード。
SQLite3を使う時、この分岐の中は充分なユニットテストはできない
"""
<span class="hljs-keyword">return</span> settings.DATABASES[<span class="hljs-string">'default'</span>][<span class="hljs-string">'ENGINE'</span>].endswith(<span class="hljs-string">'.mysql'</span>)
def db_engine_is_sqlite3():
"""
DBエンジンが SQLite3 か
テストの時用
"""
<span class="hljs-keyword">return</span> settings.DATABASES[<span class="hljs-string">'default'</span>][<span class="hljs-string">'ENGINE'</span>].endswith(<span class="hljs-string">'.sqlite3'</span>)
def _replace_sql_named_placeholder(<span class="hljs-keyword">sql</span>: str) <span class="hljs-operator">-</span><span class="hljs-operator">></span> str:
"""
名前付きプレースホルダーを MySQL形式 -> SQLite形式に置換する
- MySQL形式: `SELECT * FROM user WHERE email = %(email)s`
- SQLite形式: `SELECT * FROM user WHERE email = :email`
>>> _replace_sql_named_placeholder("customer_id <span class="hljs-operator">=</span> <span class="hljs-operator">%</span>(customer_id)s")
"customer_id <span class="hljs-operator">=</span> :customer_id"
>>> _replace_sql_named_placeholder(
... "<span class="hljs-keyword">AND</span> (email <span class="hljs-operator">=</span> <span class="hljs-operator">%</span>(email_1)s <span class="hljs-keyword">OR</span> email <span class="hljs-operator">=</span> <span class="hljs-operator">%</span>(email_2)s)")
"<span class="hljs-keyword">AND</span> (email <span class="hljs-operator">=</span> :email_1 <span class="hljs-keyword">OR</span> email <span class="hljs-operator">=</span> :email_2)"
"""
<span class="hljs-keyword">pattern</span> <span class="hljs-operator">=</span> re.compile(r<span class="hljs-string">'%\(([a-zA-Z0-9_]+)\)s'</span>)
<span class="hljs-keyword">return</span> pattern.sub(r":\1", <span class="hljs-keyword">sql</span>)
GROUP_CONCAT <span class="hljs-operator">=</span> <span class="hljs-string">'GROUP_CONCAT'</span> # GROUP_CONCATは置換対象にしない
def _replace_sql_concat(<span class="hljs-keyword">sql</span>: str) <span class="hljs-operator">-</span><span class="hljs-operator">></span> str:
"""
CONCAT を || に、簡易的に置換する
>>> _replace_sql_concat("A <span class="hljs-operator">=</span> CONCAT(<span class="hljs-operator">%</span>s, <span class="hljs-string">'hoge'</span>) <span class="hljs-keyword">AS</span> b")
"A <span class="hljs-operator">=</span> <span class="hljs-operator">%</span>s <span class="hljs-operator">||</span> <span class="hljs-string">'hoge'</span> <span class="hljs-keyword">AS</span> b"
>>> _replace_sql_concat(
... "CONCAT(<span class="hljs-string">'ebook-continuation-'</span>, t1.asp_title_code) <span class="hljs-keyword">AS</span> ()")
"<span class="hljs-string">'ebook-continuation-'</span> <span class="hljs-operator">||</span> t1.asp_title_code <span class="hljs-keyword">AS</span> ()"
>>> _replace_sql_concat(
... "GROUP_CONCAT(<span class="hljs-string">'ebook-continuation'</span>, <span class="hljs-string">'a'</span>) <span class="hljs-keyword">as</span> ()")
"GROUP_CONCAT(<span class="hljs-string">'ebook-continuation'</span>, <span class="hljs-string">'a'</span>) <span class="hljs-keyword">as</span> ()"
"""
splited_texts <span class="hljs-operator">=</span> []
re_concat <span class="hljs-operator">=</span> re.compile(r<span class="hljs-string">'CONCAT\(([^)]+),([^)]+)\)'</span>)
<span class="hljs-keyword">for</span> s <span class="hljs-keyword">in</span> sql.split(GROUP_CONCAT):
splited_texts.append(re_concat.sub(r<span class="hljs-string">'\1 ||\2'</span>, s))
<span class="hljs-keyword">return</span> GROUP_CONCAT.join(splited_texts)
def _replace_sql_concat_separator(<span class="hljs-keyword">sql</span>: str) <span class="hljs-operator">-</span><span class="hljs-operator">></span> str:
"""
GROUP_CONCAT(category_id SEPARATOR ',')
↓
GROUP_CONCAT(category_id, ',')
>>> _replace_sql_concat_separator("(category_id SEPARATOR <span class="hljs-string">','</span>)")
"(category_id, <span class="hljs-string">','</span>)"
>>> _replace_sql_concat_separator(
... "GROUP_CONCAT(category_id <span class="hljs-keyword">order</span> <span class="hljs-keyword">by</span> sort_key)")
'GROUP_CONCAT(category_id)'
"""
<span class="hljs-keyword">sql</span> <span class="hljs-operator">=</span> sql.replace(<span class="hljs-string">'group_concat'</span>, <span class="hljs-string">'GROUP_CONCAT'</span>)
<span class="hljs-keyword">sql</span> <span class="hljs-operator">=</span> re.sub(
<span class="hljs-string">'GROUP_CONCAT\((.*) order by [^)]+\)'</span>, # NOQA
r<span class="hljs-string">'GROUP_CONCAT(\1)'</span>, <span class="hljs-keyword">sql</span>)
<span class="hljs-keyword">sql</span> <span class="hljs-operator">=</span> sql.replace(<span class="hljs-string">' SEPARATOR'</span>, <span class="hljs-string">','</span>).replace(<span class="hljs-string">' separator'</span>, <span class="hljs-string">','</span>)
<span class="hljs-keyword">return</span> <span class="hljs-keyword">sql</span>
def _replace_sql_now(<span class="hljs-keyword">sql</span>: str) <span class="hljs-operator">-</span><span class="hljs-operator">></span> str:
"""
NOW() を DATE('now') に変換する
>>> _replace_sql_now("<span class="hljs-keyword">AND</span> NOW() <span class="hljs-operator"><=</span> c.coupon_end_date")
"<span class="hljs-keyword">AND</span> <span class="hljs-type">DATE</span>(<span class="hljs-string">'now'</span>) <span class="hljs-operator"><=</span> c.coupon_end_date"
"""
<span class="hljs-keyword">return</span> sql.replace(<span class="hljs-string">'NOW()'</span>, "DATE('now')")
def _replace_sql_subdate(<span class="hljs-keyword">sql</span>: str) <span class="hljs-operator">-</span><span class="hljs-operator">></span> str:
"""
SUBDATE(NOW() を DATE('now' +第2引数に変換
subdate で INTERVAL が正の値のみサポート。必要に応じて拡張する
必ず _replace_sql_now より先に行う
>>> _replace_sql_subdate(
... "d <span class="hljs-operator"><</span> SUBDATE(NOW(), <span class="hljs-type">INTERVAL</span> <span class="hljs-number">8</span> <span class="hljs-keyword">DAY</span>) <span class="hljs-comment">/* コメ */</span>")
"d <span class="hljs-operator"><</span> <span class="hljs-type">DATE</span>(<span class="hljs-string">'now'</span>, <span class="hljs-string">'-8 DAY'</span>) <span class="hljs-comment">/* コメ */</span>"
>>> _replace_sql_subdate(
... "d <span class="hljs-operator"><</span> SUBDATE(<span class="hljs-string">'2021-05-01'</span>, <span class="hljs-type">INTERVAL</span> <span class="hljs-number">8</span> <span class="hljs-keyword">DAY</span>) <span class="hljs-comment">/* コメ */</span>")
"d <span class="hljs-operator"><</span> <span class="hljs-type">DATE</span>(<span class="hljs-string">'2021-05-01'</span>, <span class="hljs-string">'-8 DAY'</span>) <span class="hljs-comment">/* コメ */</span>"
>>> _replace_sql_subdate(
... "d <span class="hljs-operator"><</span> SUBDATE(<span class="hljs-operator">%</span>s, <span class="hljs-type">INTERVAL</span> <span class="hljs-number">8</span> <span class="hljs-keyword">DAY</span>) <span class="hljs-comment">/* コメ */</span>")
"d <span class="hljs-operator"><</span> <span class="hljs-type">DATE</span>(<span class="hljs-operator">%</span>s, <span class="hljs-string">'-8 DAY'</span>) <span class="hljs-comment">/* コメ */</span>"
>>> _replace_sql_subdate(
... "d <span class="hljs-operator"><</span> SUBDATE(:placeholder, <span class="hljs-type">INTERVAL</span> <span class="hljs-number">8</span> <span class="hljs-keyword">DAY</span>) <span class="hljs-comment">/* コメ */</span>")
"d <span class="hljs-operator"><</span> <span class="hljs-type">DATE</span>(:placeholder, <span class="hljs-string">'-8 DAY'</span>) <span class="hljs-comment">/* コメ */</span>"
"""
re_subdate <span class="hljs-operator">=</span> re.compile(r<span class="hljs-string">'SUBDATE\(NOW\(\), INTERVAL (\d+) (\w+)\)'</span>)
<span class="hljs-keyword">sql</span> <span class="hljs-operator">=</span> re_subdate.sub(r"DATE('now', '-\1 \2')", <span class="hljs-keyword">sql</span>)
re_subdate <span class="hljs-operator">=</span> re.compile(r<span class="hljs-string">'SUBDATE\(([^,]+), INTERVAL (\d+) (\w+)\)'</span>)
<span class="hljs-keyword">sql</span> <span class="hljs-operator">=</span> re_subdate.sub(r"DATE(\1, '-\2 \3')", <span class="hljs-keyword">sql</span>)
<span class="hljs-keyword">return</span> <span class="hljs-keyword">sql</span>
def _replace_sql_force_index(<span class="hljs-keyword">sql</span>: str) <span class="hljs-operator">-</span><span class="hljs-operator">></span> str:
"""
FORCE INDEX (index_name_01) をINDEXED BY index_name_01 に変換する
>>> _replace_sql_force_index('FORCE INDEX (index_name_01) ')
'INDEXED BY index_name_01 '
"""
re_index_name <span class="hljs-operator">=</span> re.compile(r<span class="hljs-string">'FORCE INDEX \(([^)]+)\)'</span>)
<span class="hljs-keyword">sql</span> <span class="hljs-operator">=</span> re_index_name.sub(r<span class="hljs-string">'INDEXED BY \1'</span>, <span class="hljs-keyword">sql</span>)
<span class="hljs-keyword">return</span> <span class="hljs-keyword">sql</span>
def _replace_sql_word_boundary(<span class="hljs-keyword">sql</span>: str) <span class="hljs-operator">-</span><span class="hljs-operator">></span> str:
"""
MySQLの正規表現のワード境界をSQLite3の正規表現に変換する
>>> _replace_sql_word_boundary("[[:<span class="hljs-operator"><</span>:]]<span class="hljs-number">123</span>[[:<span class="hljs-operator">></span>:]]")
"\\b123\\b"
>>> _replace_sql_word_boundary("[[:<span class="hljs-operator"><</span>:]]<span class="hljs-number">123</span>あいうえお[[:<span class="hljs-operator">></span>:]]")
"\\b123あいうえお\\b"
>>> _replace_sql_word_boundary("[[:<span class="hljs-operator"><</span>:]]<span class="hljs-number">123</span>あいうえおABC[[:<span class="hljs-operator">></span>:]]")
"\\b123あいうえおABC\\b"
"""
re_str <span class="hljs-operator">=</span> re.compile(r<span class="hljs-string">'\[\[:<:]](.*)\[\[:>:]]'</span>)
<span class="hljs-keyword">return</span> re_str.sub(r<span class="hljs-string">'\\b\1\\b'</span>, <span class="hljs-keyword">sql</span>)
def replace_sql_mysql_to_sqlite(<span class="hljs-keyword">sql</span>: str) <span class="hljs-operator">-</span><span class="hljs-operator">></span> str:
"""
SQL文を MySQL から SQLite にできるだけ置換する。
ユニットテスト用。完全な置換はできない。
"""
<span class="hljs-keyword">sql</span> <span class="hljs-operator">=</span> _replace_sql_named_placeholder(<span class="hljs-keyword">sql</span>)
<span class="hljs-keyword">sql</span> <span class="hljs-operator">=</span> _replace_sql_concat(<span class="hljs-keyword">sql</span>)
<span class="hljs-keyword">sql</span> <span class="hljs-operator">=</span> _replace_sql_concat_separator(<span class="hljs-keyword">sql</span>)
<span class="hljs-keyword">sql</span> <span class="hljs-operator">=</span> _replace_sql_subdate(<span class="hljs-keyword">sql</span>)
<span class="hljs-keyword">sql</span> <span class="hljs-operator">=</span> _replace_sql_now(<span class="hljs-keyword">sql</span>)
<span class="hljs-keyword">sql</span> <span class="hljs-operator">=</span> _replace_sql_force_index(<span class="hljs-keyword">sql</span>)
<span class="hljs-keyword">sql</span> <span class="hljs-operator">=</span> _replace_sql_word_boundary(<span class="hljs-keyword">sql</span>)
<span class="hljs-keyword">return</span> <span class="hljs-keyword">sql</span>
def replace_sql_mysql_to_sqlite_if_sqlite_test(<span class="hljs-keyword">sql</span>: str) <span class="hljs-operator">-</span><span class="hljs-operator">></span> str:
"""
もし、DB エンジンが SQLite3 なら、SQL を SQLite3 用に置換する。
ユニットテスト用。非常に簡易的なものなので、
置換できないような複雑なSQLをユニットテストで使う場合は
@skipIf(not db_engine_is_mysql(), 'MySQL only.') でスキップする
"""
if db_engine_is_mysql():
<span class="hljs-keyword">return</span> <span class="hljs-keyword">sql</span>
<span class="hljs-keyword">return</span> replace_sql_mysql_to_sqlite(<span class="hljs-keyword">sql</span>)
</code></pre>2023年のTORICOの社内勉強会の内容2023-12-09T11:54:35+00:002024-03-19T03:14:29+00:00四柳剛https://tech.torico-corp.com/blog/author/yotsuyanagi/https://tech.torico-corp.com/blog/torico-2023-in-house-study-sessions/<p>TORICOでは、毎月1回のペースで開発者による技術勉強会を行っています。</p>
<p>2023年に行った勉強会の内容を書きます。</p>
<h2>2023-01-13</h2>
<h3>ターミナルを使ってみよう</h3>
<p><a href="https://github.com/0maru" target="_blank">@0maru</a></p>
<p>使っているコマンドラインツールの紹介を行いました。</p>
<p><a href="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/in-house-session/torico-lt-terminal.pdf" target="_blank">スライドを見る</a></p>
<ul>
<li><a href="https://github.com/junegunn/fzf" target="_blank">fzf</a></li>
<li><a href="https://github.com/peco/peco" target="_blank">peco</a></li>
<li><a href="https://github.com/sh-shell/sh-shell" target="_blank">sh shell</a></li>
<li><a href="https://github.com/withfig/autocomplete" target="_blank">fig</a></li>
<li><a href="https://github.com/marlonrichert/zsh-autocomplete" target="_blank">zsh-autocomplete</a></li>
<li><a href="https://github.com/zsh-users/zsh-autosuggestions" target="_blank">zsh-autosuggestions</a></li>
<li><a href="https://github.com/x-motemen/ghq" target="_blank">ghq</a> (git)</li>
<li><a href="https://github.com/ajeetdsouza/zoxide" target="_blank">zoxide</a> (cd)</li>
<li><a href="https://github.com/asdf-vm/asdf" target="_blank">asdf</a> (*Env)</li>
<li><a href="https://github.com/Homebrew/brew" target="_blank">Homebrew</a> (brew bundle便利)</li>
<li><a href="https://github.com/lra/mackup" target="_blank">mackup</a> (設定ファイルの同期)</li>
<li><a href="https://github.com/ogham/exa" target="_blank">exa</a> (ls)</li>
<li><a href="https://github.com/BurntSushi/ripgrep" target="_blank">ripgrep</a> (grep)</li>
<li><a href="https://github.com/sharkdp/bat" target="_blank">bat</a> (cat)</li>
<li><a href="https://github.com/sharkdp/fd" target="_blank">fd</a> (nd)</li>
</ul>
<h2>2023-02-10</h2>
<h3>Nuxt3 のチュートリアル</h3>
<p><a href="https://github.com/ytyng" target="_blank">@ytyng</a></p>
<p>Nuxt3 のリリースにあたり、ハンズオン形式で状態管理の学習を行いました。</p>
<p><a href="https://github.com/torico-tokyo/nuxt3-triple-counter-tutorial" target="_blank">Github リポジトリ</a></p>
<ul>
<li>コンポーネントのローカル変数を使った状態管理</li>
<li>コンポーザブルを使ったグローバルな状態管理</li>
<li>props/emits を使ってコンポーネント間のレスポンシブな状態の連携</li>
</ul>
<h2>2023-03-10</h2>
<h3>Flutter の 状態管理ライブラリのチュートリアル</h3>
<p><a href="https://github.com/shimizu-saffle" target="_blank">@shimizu-saffle</a> <a href="https://github.com/0maru" target="_blank">@0maru</a></p>
<p><a href="https://github.com/torico-tokyo/flutter_quadruple_counter" target="_blank">Githubリポジトリ</a> (プライベート)</p>
<p>Flutter 開発環境のセットアップと、複数の方式を用いて状態管理を使うモバイルアプリを作成しました。</p>
<ul>
<li>StatefulWidget, State, setState()</li>
<li>flutter_hooks, HookWidget, useState()</li>
<li>flutter_riverpod, ConsumerWidget, StateProvider</li>
<li>hooks_riverpod, HookConsumerWidget, useState() + StateProvider</li>
</ul>
<h2>2023-04-14</h2>
<h3>新入社員向け 会社のサービスのサーバ環境の解説</h3>
<p><a href="https://github.com/ytyng" target="_blank">@ytyng</a></p>
<p>AWS 上のサーバー構成の説明</p>
<h3>業務でよく使うコマンドの話</h3>
<p><a href="https://github.com/ytyng" target="_blank">@ytyng</a></p>
<ul>
<li><a href="https://tech.torico-corp.com/blog/socks5-browser-foreign-ip-address-by-selenium/" target="_blank">ssh で socks5プロキシを使う方法</a></li>
<li><a href="https://tech.torico-corp.com/blog/tmux-useful-script-for-ssh-session/" target="_blank">tmux の操作方法</a></li>
<li>vim の操作方法は vimtutor で学習する</li>
</ul>
<h3>リカージョンを受講しての感想</h3>
<p><a href="https://github.com/YusukeTsukahara" target="_blank">@YusukeTsukahara</a></p>
<p>TORICO で開発者教育に採用している<a href="https://recursionist.io/" target="_blank">リカージョン</a>を一通り受講しての感想の発表。</p>
<h3>3blue1brown の動画の作成方法</h3>
<p><a href="https://github.com/ytyng" target="_blank">@ytyng</a></p>
<p><a href="https://www.youtube.com/@3Blue1BrownJapan/videos" target="_blank">3blue1brown 日本語訳</a> を見ていて気になって調べた動画の作成方法の話。</p>
<p><a href="https://github.com/3b1b/manim/" target="_blank">Manim</a> という描画ライブラリがある。</p>
<h2>2023-05-12</h2>
<h3>LLM (大規模言語モデル)のプロンプトエンジニアリングガイドを読んでみよう</h3>
<p><a href="https://github.com/ytyng" target="_blank">@ytyng</a></p>
<p><a href="https://www.promptingguide.ai/" target="_blank">プロンプトエンジニアリングガイド</a>を教材としての学習。</p>
<p><a href="https://github.com/torico-tokyo/prompting-tutorial" target="_blank">Githubリポジトリ</a> (プライベート)</p>
<h3>GraphQL 解説</h3>
<p><a href="https://github.com/ytyng" target="_blank">@ytyng</a></p>
<p>Graph QL の社内での利用シーンの解説</p>
<p><a href="https://github.com/torico-tokyo/graphql-tutorial" target="_blank">Github リポジトリ</a> <span>(プライベート)</span></p>
<h2>2023-06-09</h2>
<h3>SadServers を攻略する</h3>
<p><a href="https://github.com/shitobe" target="_blank">@shitobe</a></p>
<p>サーバーのトラブル対応についての演習サイト「<a href="https://sadservers.com/scenarios" target="_blank">SadServers</a>」の紹介と、参加者による攻略を行いました。</p>
<p>SadServersは、例えば「ずっとログファイルに内容が追加され続けている。止めろ」とか「アクセスログの中から、一番多くアクセスのあるIPアドレスを探せ」などのような課題を達成するサイトです。Web で実際のサーバが起動し、ターミナルログインして問題解決します。</p>
<h3>Next.js でアプリを作って Vercel にデプロイする</h3>
<p><a href="https://github.com/0maru" target="_blank">@0maru</a></p>
<p>Next.js で Web アプリを開発し、それを Vercel にデプロイする演習を行いました。</p>
<p><a href="https://github.com/0maru/vercel-nextjs-sample" target="_blank">Github リポジトリ</a></p>
<h2>2023-07-14</h2>
<h3>Kubernetes GUI クライアントの話</h3>
<p><a href="https://github.com/ytyng" target="_blank">@ytyng</a></p>
<ul>
<li><a href="https://www.rancher.com/" target="_blank">Rancher</a></li>
<li><a href="https://github.com/MuhammedKalkan/OpenLens" target="_blank">OpenLens</a></li>
<li><a href="https://github.com/derailed/k9s" target="_blank">K9s</a></li>
<li><a href="https://docs.devtron.ai/" target="_blank">Devtron</a></li>
</ul>
<p>上記アプリの紹介。<br/>内容は後日<a href="https://tech.torico-corp.com/blog/kubernetes-gui-client/" target="_blank">ブログ記事</a>にしました。</p>
<h3>SolidPython を使って、Github のコントリビューショングラフを 3Dプリントする</h3>
<p><a href="https://github.com/ytyng" target="_blank">@ytyng</a></p>
<p>Githubのコントリビューショングラフ(草)のSVGをパースし、3Dモデル化して3Dプリンタで出力するデモです。</p>
<p><a href="https://github.com/SolidCode/SolidPython" target="_blank">SolidPython</a> を使いました。</p>
<p><a href="https://github.com/ytyng/solidpython-tutorial" target="_blank">Githubリポジトリ</a></p>
<h2>2023-08-18</h2>
<h3>hono と Cloudflare Workers の ハンズオン</h3>
<p><a href="https://github.com/0maru" target="_blank">@0maru</a></p>
<p><a href="https://github.com/honojs/hono">hono(webフレームワーク)</a><span><span> </span>で作成したWebサーバを Cloudflare Workers にデプロイするハンズオンを行いました。</span></p>
<p><a href="https://github.com/0maru/cloudflare-workers-hono" target="_blank">Github リポジトリ</a></p>
<h2>2023-09</h2>
<p>9月はオフィス移転を優先させたため、社内勉強会は行いませんでした。</p>
<h2>2023-10-13</h2>
<h3>ブログのフィーチャー画像を生成AIで作る</h3>
<p><a href="https://github.com/ytyng" target="_blank">@ytyng</a></p>
<p>ブログの内容を ChatGPT で要約させ、Stable Diffusion のプロンプトを作り、ブログのフィーチャー画像を完全に自動的に作るデモを行いました。</p>
<h3>ChatGPT 同士で会話させる</h3>
<p><a href="https://github.com/ytyng" target="_blank">@ytyng</a></p>
<p>1つの Slack API で、それぞれ個性を持ったChatGPT同士を会話させるプロンプティングの実例デモを行いました。</p>
<h2>2023-11-10</h2>
<h3>社内のセキュリティーについての取り組みの話</h3>
<p><a href="https://github.com/ytyng" target="_blank">@ytyng</a></p>
<p><a href="https://cve.mitre.org/" target="_blank">CVE</a>, <a href="https://cwe.mitre.org/" target="_blank">CWE</a>, CVSS の解説</p>
<p><a href="https://www.ipa.go.jp/security/vuln/scap/cwe.html" target="_blank">IPAの脆弱性検索ページ</a>のほうが使いやすい</p>
<p><a href="https://www.security-next.com/137747/2" target="_blank">2022年における危険な脆弱性タイプのトップ25が明らかに</a></p>
<p><a href="https://jvn.jp/nav/jvn.html" target="_blank">JVN</a> 日本の脆弱性データベース</p>
<p><a href="https://jvndb.jvn.jp/" target="_blank">JVN Pedia CVE</a> の翻訳サイトだと思えばよい</p>
<p><a href="https://www.security-next.com/" target="_blank">Security Next</a></p>
<p><a href="https://www.publickey1.jp/" target="_blank">PublicKey</a></p>
<p><a href="https://ap-northeast-1.console.aws.amazon.com/ecr/repositories?region=ap-northeast-1" target="_blank">AWS ECR スキャン</a></p>
<p><a href="https://github.com/pypa/pip-audit" target="_blank">PyPA pip-audit</a></p>
<p><a href="https://www.techmatrix.co.jp/product/appscan/index.html" target="_blank">AppScan</a></p>
<p><a href="https://www.zaproxy.org/" target="_blank">OWASP Zap</a></p>
<p><a href="https://juiceshop-w.torico-tokyo.com/" target="_blank">OWASP JuiceShop</a></p>
<p>社内のやられアプリの紹介</p>
<p>社内のセキュアコーディングに関する規約の解説</p>
<p><a href="https://www.amazon.co.jp/dp/4797393165" target="_blank">体系的に学ぶ 安全なWebアプリケーションの作り方 第2版</a></p>
<h2>2023-12-08</h2>
<h3>astro でブログを作ってみよう</h3>
<p><a href="https://github.com/0maru" target="_blank">@0maru</a></p>
<p><a href="https://astro.build/" target="_blank">astro</a> フレームワークを使って、記事リストとタグ検索を備えたブログの静的配信システムを1時間で作るチュートリアルを行いました。</p>
<p><a href="https://github.com/0maru/astro-tutorial" target="_blank">Githubリポジトリ</a></p>
<p>あるなしクイズつき</p>ubuntuのインプレースアップデートの手順書2023-11-15T03:19:09+00:002024-03-19T07:20:17+00:00工藤淳https://tech.torico-corp.com/blog/author/kudou/https://tech.torico-corp.com/blog/ubuntu%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%82%B9%E3%82%A2%E3%83%83%E3%83%97%E3%83%87%E3%83%BC%E3%83%88%E3%81%AE%E6%89%8B%E9%A0%86%E6%9B%B8/<h1 class="page-title">Ubuntu をインプレースアップデート<span>した</span></h1>
<div class="page-body">
<p>バージョンが <code>18</code> のUbuntuで動作している社内サーバーが残っていたのアップデートしました。</p>
<p class="" id="50213dd9-49b3-4f86-bd00-3f44461f080c">どうせなら <code>22</code> にしたいのですが、<code>18 → 22</code> のようなバージョンをとばしたアップデートはできません。</p>
<p class="" id="8dfa08e2-8604-483f-8bda-0a453ef430f9"><code>18 → 20</code>、<code>20 → 22</code>と1段階ずつアップデートしていきます。</p>
<p class="" id="65c881b1-d4bb-42f3-a9bb-10b67cc69650"></p>
<p class="" id="59dd4cb5-889a-42b1-90e3-482b8b8c6f84">現在のバージョンの確認</p>
<pre class="code" id="3887fdfd-edbc-464c-9c74-dd9b0f891ddc"><code>cat /etc/os-release</code></pre>
<p class="" id="fcfe8c24-c1fc-43ff-aa86-42a3cee1bd66">アップグレード可能なバージョンの確認</p>
<pre class="code" id="c3fa86d6-8db4-4804-a765-6d541c035583"><code>sudo do-release-upgrade -c</code></pre>
<p class="" id="793443b7-7996-4d4c-827b-463de4d066d5">インストール済みのaptパッケージの更新と不要なパッケージ、キャッシュのされているdepファイルの削除。エラーがでた場合は個別にコマンドを実行します</p>
<pre class="code" id="74da7fec-3d26-4427-8c21-d9dfac2b61f9"><code>sudo apt update -y && sudo apt upgrade -y && sudo apt autoremove -y && sudo apt autoclean -y</code></pre>
<p class="" id="f0103604-2d0a-4c87-8739-316272dd8bd7">アップデートが残っていて、適用されたならば一度再起動しておきます</p>
<pre class="code" id="42bfcc06-ee0b-4db4-917b-ea4c06f9e969"><code>sudo reboot</code></pre>
<p class="" id="c4e455dd-f54f-4c23-a585-a6f469a38e6a">Ubuntu をアップグレードするコマンドを実行</p>
<pre class="code" id="1157de81-4d9f-4a06-bcd7-c3cabe3e1425"><code>sudo do-release-upgrade</code></pre>
<p class="" id="5c187211-0b88-43e6-8e60-319e0dd24ac3"></p>
<p class="" id="23401f78-e558-4df7-af29-51ffa4c8e5f4">以下はアップグレード時の質問事項</p>
<p class="" id="1d8ab659-769a-43e4-b877-a3a49fd606a1">ssh接続の追加ポートとして1022ポートを使用しできるようにするか?という質問は<code>yes</code><br/> アップデート中のトラブル時に再接続のために使用できる。<br/>その後に表示されるコマンドはメモしておく。</p>
<pre class="code code-wrap" id="7e5dd483-c49d-4b46-9afb-d46108165815"><code>If you continue, an additional ssh daemon will be started at port
'1022'.
Do you want to continue?
Continue [yN]</code></pre>
<p class="" id="7ca1b7d8-fcb2-4ba6-8360-370f829c2887">ubuntuにパッケージリポジトリに含まれていないサードパーティ製のアプリの無効化の通知。 <br/>これは通知のみなので<code>enter</code></p>
<pre class="code code-wrap" id="d0008c3c-3048-4717-ae2a-404743e85f7f"><code>Third party sources disabled</code></pre>
<p class="" id="906767f7-4bef-405a-a972-a93cbe7c0619">OSのバージョンに合わせてパッケージをアップグレードを実行してよいか?<br/>パッケージの詳細を確認したい場合は<code>d</code>、進めるには<code>yes</code></p>
<pre class="code code-wrap" id="791d2b54-0e66-4026-933d-64265a6f8e15"><code>Do you want to start the upgrade?
〜更新されるパッケージの一覧の表示〜
Installing the upgrade can take several hours. Once the download has
finished, the process cannot be canceled.
Continue [yN] Details [d]</code></pre>
<p class="" id="993cfa4d-c41f-4458-a7fa-c7778c2f9f8d">再起動する必要があるパッケージがインストール、更新された際に自動で再起動してよいか?<br/> 基本は<code>OK</code>で</p>
<pre class="code code-wrap" id="a0d82227-6535-4e6e-acdb-a146ca8c232c"><code>There are services installed on your system which need to be restarted when certain libraries, such as libpam, libc, and
libssl, are upgraded. Since these restarts may cause interruptions of service for the system, you will normally be prompted
on each upgrade for the list of services you wish to restart. You can choose this option to avoid being prompted; instead,
all necessary restarts will be done for you automatically so you can avoid being asked questions on each library upgrade.
Restart services during package upgrades without asking?</code></pre>
<p class="" id="4bfb0378-09fc-4209-a3c1-15f6f3c078f5">LXDをアップグレードするか? <code>yes</code><br/> 必要がない場合はLXDに関する質問は聞かれない</p>
<pre class="code code-wrap" id="3786c279-f040-4865-aad7-1bec6dfbee39"><code>Upgrade to the LXD snap
Starting with LXD 3.1, all new releases of LXD are only available to Ubuntu users through the snap package.
This package update will transition your system over to the snap by installing it and then running an automated migration
tool.
As part of this upgrade, all containers will briefly be shutdown and brought back up. Before continuing, make sure that you
are ready for this downtime.</code></pre>
<p class="" id="313d59fb-f48b-41e7-a83a-26b35b710efd">LXDのバージョンをどうするか?<br/> 基本は新しいバージョンで。今回の場合は<code>4.0</code> を選択</p>
<pre class="code code-wrap" id="3e6afee9-8698-4b85-9f71-770fae48e197"><code>The LXD project puts out monthly feature releases which while backward compatible at an API and CLI level, will contain
some behavior change and potentially require manual intervention during an upgrade.
In addition to those, every 2 years a LTS release is made which comes with 5 years of support through frequent bugfix-only
releases.
The LXD team recommends you pick "4.0" for production environments and use "latest" if you're interested in getting the
latest LXD features.</code></pre>
<p class="" id="4f6610b4-c544-4e97-9424-df76cfe29d15">構成ファイル <code>sshd_config</code> をどうするか?<br/>現在の設定をそのまま使用するので<code>keep the local version currently installed</code> を選択</p>
<pre class="code code-wrap" id="bf8ee4f2-e662-4489-a139-2175967a7c02"><code>A new version (/tmp/file4mEdmb) of configuration file /etc/ssh/sshd_config is available, but the version installed
currently has been locally modified.
What do you want to do about modified configuration file sshd_config?
</code></pre>
<p class="" id="dc7df46d-214c-4f37-990b-00f442bb5826">アップグレード開始 終了後に古いパッケージの削除をするか?<br/> 古いパッケージは不要なので<code>yes</code></p>
<pre class="code code-wrap" id="76e6c9b3-d3ff-47b9-bc14-42392c0c0f18"><code>Reading package lists... Done
Building dependency tree
Reading state information... Done
Searching for obsolete software
Reading state information... Done
Remove obsolete packages?</code></pre>
<p class="" id="33852c91-ba11-458a-b7b5-60c400a7b097">アップデートの完了<br/> 再起動するか?はもちろん<code>yes</code></p>
<pre class="code code-wrap" id="122ea31b-e688-415e-96dd-c52c6d9ec1f6"><code>System upgrade is complete.
Restart required
To finish the upgrade, a restart is required.
If you select 'y' the system will be restarted.
Continue [yN]</code></pre>
<p class="" id="af030e66-97f5-438a-9b7e-31ddcbf6f767">再起動後にサーバーに接続して現在のバージョンを確認する</p>
<pre class="code code-wrap" id="fe98c69f-d61c-4196-8fa1-4ff9ef5e21b8"><code>cat /etc/os-release</code></pre>
<pre>PRETTY_NAME="Ubuntu 22.04.3 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.3 LTS (Jammy Jellyfish)"
</pre>
<p class="" id="5fc333c8-0ed1-4e80-a6cc-4f22bbeb9eff">バージョンが上がっていることを確認。<br/> 一通り操作して動作することを確認。<br/> 必要なパッケージがあれば追加でインストールしてアップデートは完了。<br/> 今回は<code>18 → 22</code>に上げたのでもう一度同じ操作を行なっています。</p>
<p class="" id="1e85ee94-1f8d-4281-afaa-28ad8d851b9c"></p>
<p class="" id="94ec3839-0baf-42c0-806b-b93d0c649bd0"><strong>注意ポイントその1</strong></p>
<p class="" id="0137a543-c002-4300-9e44-c6906248ee70">ubuntu 22 にfuseをインストール、更新するとシステムが壊れる可能性があるのでインストールしない。<code>N</code>を選択。</p>
<pre class="code" id="6aaec1cb-c6e1-41c0-917c-175dd4c2a80d"><code>Configuration file '/etc/fuse.conf'
==> Modified (by you or by a script) since installation.
==> Package distributor has shipped an updated version.
What would you like to do about it ? Your options are:
Y or I : install the package maintainer's version
N or O : keep your currently-installed version
D : show the differences between the versions
Z : start a shell to examine the situation
The default action is to keep your current version.
*** fuse.conf (Y/I/N/O/D/Z) [default=N] ?</code></pre>
<p class="" id="a8fe9c49-8a93-4d78-9128-ac1e3e0b2afe">参照ページ FUSE</p>
<p class="" id="4bf55a60-8243-4aac-b5e1-13d9430bb9ab"><a href="https://github.com/AppImage/AppImageKit/wiki/FUSE">https://github.com/AppImage/AppImageKit/wiki/FUSE</a></p>
<p class="" id="72d05d0a-3467-4f6c-a19e-015b07220bc6">22に最新版をインストールすると壊れますとか物騒すぎる。</p>
<pre class="code" id="23f242eb-410a-49a9-9bb7-cf4b71e11dbd"><code>Warning: While libfuse2 is OK, do not install the fuse package as of 22.04 or you may break your system</code></pre>
<p class="" id="296e3f7c-4856-4c72-9060-4f879ebe4142"></p>
<p class="" id="94ec3839-0baf-42c0-806b-b93d0c649bd0"><strong>注意ポイントその2</strong></p>
<p class="" id="872284c1-b3b7-4043-b40c-b13d944c2f7c">メール認証の設定。<br/> 現在の設定をキープする。<code>N</code>を選択。</p>
<pre class="code" id="0479063c-da15-4a06-ae16-5ac8bda83f5a"><code>Configuration file '/etc/default/saslauthd'
==> Modified (by you or by a script) since installation.
==> Package distributor has shipped an updated version.
What would you like to do about it ? Your options are:
Y or I : install the package maintainer's version
N or O : keep your currently-installed version
D : show the differences between the versions
Z : start a shell to examine the situation
The default action is to keep your current version.
*** saslauthd (Y/I/N/O/D/Z) [default=N] ?</code></pre>
<p class="" id="b1aa2b03-b9e4-4d19-8e86-35389016c110"></p>
</div>Synology の Glacier Backup で作ったボールトから特定のファイルをコマンドラインで復旧する2023-11-04T10:55:40+00:002024-03-19T04:52:46+00:00四柳剛https://tech.torico-corp.com/blog/author/yotsuyanagi/https://tech.torico-corp.com/blog/restore-one-file-from-aws-gracier-of-synology-nas-by-aws-cli/<div>
<div class="content my-5" id="markdown-content">
<h2 id="前提">前提</h2>
<h3 id="s3-glacier">S3 Glacier</h3>
<p>S3 Glacier は、AWS のバックアップデータの保存サービスです。</p>
<p><a href="https://docs.aws.amazon.com/amazonglacier/latest/dev/introduction.html" target="_blank">What Is Amazon S3 Glacier? - Amazon S3 Glacier</a></p>
<p>以前は Glacier というサービス名でしたが、いつからか S3 Glacier という名前に変わりました。</p>
<p>S3 などのストレージを安価にアーカイブ(バックアップ)することができます。S3 以外にも、外部から API で送った内容をアーカイブできます。</p>
<p>ファイル名という概念は無く、送ったデータがアーカイブされた際は「アーカイブID」が付与されます。取得するにはアーカイブIDが必要です。</p>
<p>アーカイブしたり複合したりするには1アクションにつき5時間ぐらいかかります。</p>
<h3 id="synologyのnas">SynologyのNAS</h3>
<p><a href="https://www.synology.com/" target="_blank">Synology</a> の NAS は Web UI からアプリをインストールできるようになっており、Glacier Backup というアプリがインストールできます。</p>
<h3 id="glacier-backup-アプリ">Glacier Backup アプリ</h3>
<p><img alt="" height="652" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/glacier/glacier-03.png" width="1031"/></p>
<p>Synology の NAS にインストールした Glacier Backup を使うと、NAS を AWS S3 Glacier に定期的にバックアップすることができます。</p>
<p>また逆に、Glacier から NAS に内容すべてを復元することもできます。</p>
<p>すべて Web上のUIで行えるため、手軽でいいのですが、ファイル単位での復元ができません。</p>
<h4 id="復元時間">復元時間</h4>
<p>100GB程度のNASであれば、復元に2日ぐらいかかると思って良いでしょう。 テラバイト規模の場合は1ヶ月単位での復旧期間がかかりそうです。</p>
<h4 id="アーカイブの構造">アーカイブの構造</h4>
<p>Glacier Backup のアプリは、2つのボールトを AWS S3 Glacier 内に作ります。</p>
<p>メインとなるファイルの内容をすべて保存するボールトが1つと、メインのアーカイブのファイルのフルパス名の対応表を格納している <strong>_mapping</strong> という文言が名前の末尾につくボールトが1つ作られます。</p>
<p>_mapping のボールトには、ファイルのフルパス名とアーカイブIDの対応表が SQLite3 形式で保存されています。</p>
<p><img alt="" height="371" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/glacier/glacier-01.png" width="1244"/></p>
<h2 id="今回の要件">今回の要件</h2>
<p>Synology の Glacier Backup アプリでバックアップしたボールトから、<strong>特定の1ファイルだけ</strong>を復元します。</p>
<h2 id="復旧手順">復旧手順</h2>
<h3 id="1-マッピングのボールトのアーカイブ一覧を取得-inventoryretrieval">1. マッピングのボールトのアーカイブ一覧を取得 (InventoryRetrieval)</h3>
<p>AWS CLI で、_mapping のボールト内のアーカイブ一覧を取得します。</p>
<pre><code class="language-shell hljs">aws glacier initiate-job --account-id 012345678901 \
--vault-name my_synology_0011223344FF_1_mapping \
--job-parameters="{\"Type\":\"inventory-retrieval\"}"
</code></pre>
<p>実行すると、以下のような JSON がレスポンスされます。</p>
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
<span class="hljs-attr">"location"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"/012345678901/vaults/my_synology_0011223344FF_1_mapping/jobs/<job-id-here>"</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"jobId"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"<job-id-here>"</span>
<span class="hljs-punctuation">}</span>
</code></pre>
<p>上記の <code><job-id-here></code> の箇所にジョブIDが入っています。</p>
<p>試しに、ジョブの内容を取得します。</p>
<pre><code class="hljs language-sql">aws glacier <span class="hljs-keyword">describe</span><span class="hljs-operator">-</span>job <span class="hljs-comment">--account-id 012345678901 \</span>
<span class="hljs-comment">--vault-name my_synology_0011223344FF_1_mapping \</span>
<span class="hljs-comment">--job-id <job-id-here></span>
</code></pre>
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
<span class="hljs-attr">"JobId"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"<job-id-here>"</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"Action"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"InventoryRetrieval"</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"VaultARN"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"arn:aws:glacier:ap-northeast-1:012345678901:vaults/my_synology_0011223344FF_1_mapping"</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"CreationDate"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"2023-10-30T00:25:25.289Z"</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"Completed"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">false</span></span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"StatusCode"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"InProgress"</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"InventoryRetrievalParameters"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
<span class="hljs-attr">"Format"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"JSON"</span>
<span class="hljs-punctuation">}</span>
<span class="hljs-punctuation">}</span>
</code></pre>
<p>まだ処理中のようです。</p>
<p>処理には5時間程度かかるので、待ちます。</p>
<p>ちなみに処理が終わってから24時間経過するとジョブの結果は消えてしいますので注意してください。</p>
<h3 id="inventory-retrieval-ジョブの結果を取得する">2. Inventory Retrieval ジョブの結果を取得する</h3>
<p>5時間経過したら、ジョブの結果を再取得してみます。</p>
<pre><code class="hljs language-sql">aws glacier <span class="hljs-keyword">describe</span><span class="hljs-operator">-</span>job <span class="hljs-comment">--account-id 012345678901 \</span>
<span class="hljs-comment">--vault-name my_synology_0011223344FF_1_mapping \</span>
<span class="hljs-comment">--job-id <job-id-here></span>
</code></pre>
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
<span class="hljs-attr">"JobId"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"<job-id-here>"</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"Action"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"InventoryRetrieval"</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"VaultARN"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"arn:aws:glacier:ap-northeast-1:012345678901:vaults/my_synology_0011223344FF_1_mapping"</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"CreationDate"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"2023-10-30T00:25:25.289Z"</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"Completed"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"StatusCode"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Succeeded"</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"CompletionDate"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"2023-10-30T04:06:58.738Z"</span>
<span class="hljs-attr">"InventoryRetrievalParameters"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
<span class="hljs-attr">"Format"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"JSON"</span>
<span class="hljs-punctuation">}</span>
<span class="hljs-punctuation">}</span>
</code></pre>
<p>ジョブが終わっていました。</p>
<h3 id="ジョブの結果を取得する">3. ジョブの結果を取得する</h3>
<p>さきほど終了したジョブの内容を表示します。</p>
<pre><code class="hljs language-lua">aws glacier get-job-<span class="hljs-built_in">output</span> <span class="hljs-comment">--account-id 012345678901 \</span>
<span class="hljs-comment">--vault-name my_synology_0011223344FF_1_mapping \</span>
<span class="hljs-comment">--job-id <job-id-here></span>
</code></pre>
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
<span class="hljs-attr">"VaultARN"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"arn:aws:glacier:ap-northeast-1:012345678901:vaults/my_synology_0011223344FF_1_mapping"</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"InventoryDate"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"2023-10-28T21:59:35Z"</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"ArchiveList"</span><span class="hljs-punctuation">:</span><span class="hljs-punctuation">[</span><span class="hljs-punctuation">{</span>
<span class="hljs-attr">"ArchiveId"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"<mapping-archive-id-here>"</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"ArchiveDescription"</span><span class="hljs-punctuation">:</span><span class="hljs-string">""</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"CreationDate"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"2023-10-28T17:25:18Z"</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"Size"</span><span class="hljs-punctuation">:</span><span class="hljs-number">76182528</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"SHA256TreeHash"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"..."</span>
<span class="hljs-punctuation">}</span><span class="hljs-punctuation">]</span>
<span class="hljs-punctuation">}</span>
</code></pre>
<p>このボールトにはアーカイブが1つだけ含まれていることがわかります。これが SQLite3のデータベースのファイルがアーカイブされたものです。</p>
<h3 id="マッピングファイルを取得可能にする">4. マッピングファイルを取得可能にする (A<span>rchiveRetrieval)</span></h3>
<p>さきほど取得した <code><mapping-archive-id-here></code> のアーカイブIDに対して <code>archive-retrieval</code> ジョブを発行し、アーカイブのファイルを取得可能にします。</p>
<pre><code class="hljs language-swift">aws glacier initiate<span class="hljs-operator">-</span>job <span class="hljs-operator">--</span>account<span class="hljs-operator">-</span>id <span class="hljs-number">012345678901</span> \
<span class="hljs-operator">--</span>vault<span class="hljs-operator">-</span>name my_synology_0011223344FF_1_mapping \
<span class="hljs-operator">--</span>job<span class="hljs-operator">-</span>parameters<span class="hljs-operator">=</span><span class="hljs-string">"{<span class="hljs-subst">\"</span>Type<span class="hljs-subst">\"</span>:<span class="hljs-subst">\"</span>archive-retrieval<span class="hljs-subst">\"</span>, <span class="hljs-subst">\"</span>ArchiveId<span class="hljs-subst">\"</span>:<span class="hljs-subst">\"</span><mapping-archive-id-here><span class="hljs-subst">\"</span>}"</span>
</code></pre>
<p>表示されたジョブIDを記録しておきます。</p>
<p>実行後、5時間待ちます。</p>
<h3 id="マッピングファイルを取得する">5. マッピングファイルを取得する</h3>
<p>さきほどと同様に <code>aws glacier describe-job</code> を行い、<code>"Completed"</code> が <code>true</code> となっていることを確認後、<code>get-job-output</code> でファイルの内容を取得します。</p>
<pre><code class="hljs language-lua">aws glacier get-job-<span class="hljs-built_in">output</span> <span class="hljs-comment">--account-id 012345678901 \</span>
<span class="hljs-comment">--vault-name my_synology_0011223344FF_1_mapping \</span>
<span class="hljs-comment">--job-id <さきほどのジョブID> ${HOME}/Downloads/my_synology_mapping.sqlite3</span>
</code></pre>
<h3 id="マッピングファイルの内容を見て、ファイル名と対応するアーカイブidを検索する">6. マッピングファイルの内容を見て、ファイル名と対応するアーカイブIDを検索する</h3>
<pre><code class="hljs language-bash">sqlite3 <span class="hljs-variable">${HOME}</span>/Downloads/my_synology_mapping.sqlite3
</code></pre>
<p>とすることで内容を確認できます。 DataGrip など GUI クライアントがあれば、そちらで開きましょう。</p>
<p><img alt="" height="446" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/glacier/glacier-02.png" width="1244"/></p>
<p><code>file_info_tb</code> テーブルに、ファイルのフルパスとアーカイブIDの対応表が入っています。 ファイル名で SELECT し、該当する アーカイブIDを検索してください。</p>
<h3 id="目当てのアーカイブを取得可能にする">7. 目当てのアーカイブを取得可能にする</h3>
<p>取得するアーカイブに対して <code>archive-retrieval</code> ジョブを発行し、ファイルを取得可能にします。</p>
<p>対象とするボールトは、 _mapping が<strong>つかない方</strong>です。</p>
<pre><code class="hljs language-swift">aws glacier initiate<span class="hljs-operator">-</span>job <span class="hljs-operator">--</span>account<span class="hljs-operator">-</span>id <span class="hljs-number">012345678901</span> \
<span class="hljs-operator">--</span>vault<span class="hljs-operator">-</span>name my_synology_0011223344FF_1 \
<span class="hljs-operator">--</span>job<span class="hljs-operator">-</span>parameters<span class="hljs-operator">=</span><span class="hljs-string">"{<span class="hljs-subst">\"</span>Type<span class="hljs-subst">\"</span>:<span class="hljs-subst">\"</span>archive-retrieval<span class="hljs-subst">\"</span>, <span class="hljs-subst">\"</span>ArchiveId<span class="hljs-subst">\"</span>:<span class="hljs-subst">\"</span><復元対象のアーカイブID><span class="hljs-subst">\"</span>}"</span>
</code></pre>
<p>他にも取得するファイルがあるなら <code>archive-retrieval</code> を発行しておきましょう。</p>
<p>実行後、5時間待ちます。</p>
<h3 id="目当てのファイルを取得する">8. 目当てのファイルを取得する</h3>
<p><code>aws glacier describe-job</code> を行い、<code>"Completed"</code> が <code>true</code> となっていることを確認後、<code>get-job-output</code> でファイルの内容を取得します。</p>
<pre><code class="hljs language-lua">aws glacier get-job-<span class="hljs-built_in">output</span> <span class="hljs-comment">--account-id 012345678901 \</span>
<span class="hljs-comment">--vault-name my_synology_0011223344FF_1 \</span>
<span class="hljs-comment">--job-id <さきほどのジョブID> ${HOME}/Downloads/取得するファイル名</span>
</code></pre>
<p>ファイル名はボールト内に保存されていないため、先程の mapping の SQLite3のデータベースの <code>file_info_tb</code> に保存されているファイル名を自分で付与します。</p>
</div>
</div>
<footer></footer>新卒エンジニア向けに、電子マンガのエンジニアリングについて話すイベントを開催します。2023-10-11T01:52:40+00:002024-03-19T03:15:35+00:00四柳剛https://tech.torico-corp.com/blog/author/yotsuyanagi/https://tech.torico-corp.com/blog/developer-event-manga-tech-talk-2023/<p>株式会社メディアドゥ、ピクシブ株式会社、株式会社ブックウォーカー、株式会社TORICO の技術部門の合同で、電子書籍の作成・流通から閲覧までの技術的裏側を紹介するイベントを行います。</p>
<p>イベント参加者は、connpass のイベントページから申し込みを行ってください。<br/><span>› </span><a href="https://mediado-go.connpass.com/event/296676/" target="_blank">connpass の マンガTechTalk のページを見る</a></p>
<h2>イベント概要</h2>
<p>「マンガTechTalk」は電子マンガ関連サービスを開発しているIT企業合同の、<br/>エンジニア学生向け業界研究・座談会イベントです!</p>
<p>みなさんは、電子マンガが執筆されてから読者が楽しむまでに<br/>どんなプロダクトやテクノロジーが利用されているか知っていますか?</p>
<p>このイベントでは、電子マンガに関わるプロダクトを開発・運営しているIT企業のエンジニアが登壇し、<br/>電子マンガビジネス全体の流れや利用しているテクノロジーをお話するパネルTalkと、<br/>実際に開発しているエンジニアとの少人数での座談会Talkを行います。</p>
<p>電子マンガ業界の全体感がつかめる!<br/>電子マンガに使われているテクノロジーがわかる!<br/>電子マンガのシステムを開発しているエンジニアと話せる!<br/>マンガに興味があって、将来エンジニアとして働くことに興味のある学生のみなさんのご参加をお待ちしています!</p>
<h2>イベント日時</h2>
<p>2023/10/25 (水) 18:30 ~ 21:00<br/>※入場受付は開始時間の30分前からを予定</p>
<h2>タイムテーブル</h2>
<table>
<thead>
<tr>
<th>時間</th>
<th>コンテンツ</th>
<th>開催形式</th>
</tr>
</thead>
<tbody>
<tr>
<td>18:30 ~ 18:35</td>
<td>オープニングTalk</td>
<td>オンライン/オフライン両方開催</td>
</tr>
<tr>
<td>18:35 ~ 19:05</td>
<td>パネルTalk「電子マンガのプロダクトを知ろう!」</td>
<td>オンライン/オフライン両方開催</td>
</tr>
<tr>
<td>19:05 ~ 19:15</td>
<td>休憩</td>
<td>オンラインの方はここで終了</td>
</tr>
<tr>
<td>19:15 ~ 20:50</td>
<td>企業別座談会Talk</td>
<td>オフライン開催のみ</td>
</tr>
<tr>
<td>20:50 ~ 21:00</td>
<td>クロージングTalk</td>
<td>オフライン開催のみ</td>
</tr>
</tbody>
</table>
<h2>コンテンツ詳細</h2>
<h3>パネルTalk「電子マンガのシステムを知ろう!」</h3>
<p>電子マンガが執筆〜読書されるまでの全体の流れを説明し、<br/>漫画家、出版社、取次、電子書店、読者それぞれに対して登壇する各社が提供しているプロダクトをご紹介!</p>
<p>ここまではオンライン/オフライン両方での参加が可能なコンテンツです。</p>
<h3>企業別座談会Talk</h3>
<p>企業ごとのスペースに分かれ、実際に働いているエンジニアと少人数の座談会を行います!<br/>企業概要、エンジニア組織、技術、プロダクトなどざっくばらんにTalkできるパートです。<br/>エンジニアの生の声を聞いてみたい方はぜひご参加ください。</p>
<p>※このコンテンツはオフライン会場のみ実施いたしますので、<br/>参加希望の方はオフライン枠にてお申し込みください。</p>
<h2>参加資格</h2>
<p>下記すべてを満たしている方</p>
<ul>
<li>高等専門学校、専門学校、大学、大学院に学生として在籍されている方</li>
<li>ITエンジニアを目指している方</li>
<li>今後就職活動をする予定の方 (卒業年度不問です)</li>
</ul>
<h2>イベント開催方法</h2>
<p>企業別座談会Talkはオフライン参加の方限定コンテンツです。<br/>オンライン参加は参加いただけませんので、ご注意ください。</p>
<p>参加希望者は、connpass のイベントのページから申し込みを行ってください。</p>
<p>› <a href="https://mediado-go.connpass.com/event/296676/" target="_blank">connpass の マンガTechTalk のページを見る</a></p>
<h3>オフライン会場</h3>
<p>メディアドゥ社オフィス(東京都千代田区一ツ橋1-1-1 パレスサイドビル 5F)<br/>竹橋駅直結(1b出口)または神保町駅(A8出口より徒歩5分)</p>
<p><a href="https://mediado.jp/about/access/" target="_blank">https://mediado.jp/about/access/</a></p>
<p>5Fセミナールーム</p>
<h3>オンライン会場</h3>
<p>参加登録者限定でのZoomオンライン配信<br/>イベント参加登録者にのみ、参加URLをお伝えします。</p>
<h1>登壇予定企業</h1>
<h3>株式会社メディアドゥ (<span> </span><a href="https://mediado.jp/" rel="nofollow" target="_blank">https://mediado.jp/</a><span> </span>)</h3>
<ul>
<li>電子書籍など出版を中心としたデジタルコンテンツ流通のサービスをメインに提供するIT企業です。</li>
<li>「ひとつでも多くのコンテンツをひとりでも多くの人に届ける」をVisionに掲げ、多くの人がコンテンツを楽しめる世界の実現を目指しています。</li>
<li>世界No.2の電子書籍流通総額を誇り、出版業界のIT企業として重要なポジションを担っています。</li>
<li>エンジニア向け採用HP(<a href="https://recruit.mediado.jp/" rel="nofollow" target="_blank">https://recruit.mediado.jp/</a>)</li>
</ul>
<h3>ピクシブ株式会社 (<span> </span><a href="https://www.pixiv.co.jp/" rel="nofollow" target="_blank">https://www.pixiv.co.jp/</a><span> </span>)</h3>
<p><a href="https://www.pixiv.co.jp/" rel="nofollow" target="_blank">ピクシブ株式会社</a>は「創作活動を、もっと楽しくする。」という理念のもと、クリエイターのためのプラットフォームを開発、運営しています。</p>
<p>そのプラットフォームの中心となる「<a href="https://www.pixiv.net/" rel="nofollow" target="_blank">pixiv</a>」は作品(イラスト・マンガ・小説)を介したコミュニケーションにフォーカスした、クリエイターのためのSNSです。2007年9月に開始され、現在9000万人を超えるユーザーが登録し、総作品数は1億作品以上にのぼり、世界中のユーザーに利用されてるプロダクトとして運営されています。</p>
<p>またpixiv以外にもCtoCの創作物の総合マーケット、「<a href="https://booth.pm/ja" rel="nofollow" target="_blank">BOOTH</a>」や創作活動を応援するためのファンコミュニティ「<a href="https://www.fanbox.cc/" rel="nofollow" target="_blank">pixivFANBOX</a>」など多くのサービスを展開しております。</p>
<h3>株式会社ブックウォーカー(<a href="https://www.bookwalker.co.jp/%EF%BC%89" rel="nofollow" target="_blank">https://www.bookwalker.co.jp/)</a></h3>
<ul>
<li>電子書籍サービスを主領域とするテックカンパニー</li>
<li>KADOKAWAグループのデジタル戦略子会社として、本のあり方、読書のあり方、出版のあり方をデジタルの力で再定義し、創造が循環し続ける社会の実現を目指している</li>
<li>電子書籍ストアの「BOOK☆WALKER」を日本や海外で展開、NTTドコモ社とともに電子雑誌読み放題「dマガジン」電子書籍サービス「dブック」を提供、マンガ上にコメントを書き込む等ユーザー参加型の読書体験が楽しめる「ニコニコ漫画」や国内最大級の書評サービス「読書メーター」などのサービスの開発・運営を行っている</li>
</ul>
<h3>株式会社TORICO(<a href="https://www.torico-corp.com/%EF%BC%89" rel="nofollow" target="_blank">https://www.torico-corp.com/)</a></h3>
<ul>
<li>株式会社TORICOは「世界を虜に」というVISIONを掲げ、エンターテインメントを通して「楽しさ」を提供し続ける会社を目指しています。</li>
<li>1巻から最終巻までの全巻セットのみを販売するECサイト「<a href="https://www.mangazenkan.com/" rel="nofollow" target="_blank">漫画全巻ドットコム</a>」、マンガを読みながらポイ活も出来るデジタルコミック配信サービス「<a href="https://www.sukima.me/" rel="nofollow">スキマ</a>」、リアル店舗を持ちマンガ作品の展示会、コラボカフェ、サイン会等を開催する「<a href="https://www.manga10.com/" rel="nofollow" target="_blank">マンガ展</a>」などを運営しています。</li>
<li>20卒からは総合職に加え、新卒エンジニア職の採用も開始しています!</li>
</ul>
<h1>登壇者紹介(順不動)</h1>
<h3>沓名 雅司さん / 株式会社メディアドゥ</h3>
<p>IP事業ソリューション本部 技術フェロー<br/>SIerでシステムの開発、設計・提案、PMを経験した後に、ERPパッケージベンダーに転職して人事や会計などのパッケージ製品開発に従事。 現在はメディアドゥの技術フェローとしてデジタルコンテンツに関わる新規事業の技術統括として様々なプロダクトを技術面から推進。<br/><img src="https://mediado.jp/wp-content/uploads/2021/04/aws-summit_2-819x1024.jpg" width="100"/></p>
<h3>柳瀬 直裕さん / 株式会社ブックウォーカー</h3>
<p>取締役CTO 2012年より電子書籍サービスBOOK☆WALKERの開発・マネージメントを担当。2017年より台湾漫読股份有限公司に兼務出向し台湾BOOK☆WALKER開発管理も担当。2021年にCTO就任、各種サービス開発と組織のマネージメントを務める。</p>
<h3>佐々木佳祐さん / ピクシブ株式会社</h3>
<p>コミック事業部 事業部長 2014年新卒入社。Android/iOSアプリエンジニアを経て、pixivコミック、Palcyのプロダクトマネージャーを経験。<br/>現在はコミック事業部の事業部長として「ピクシブのマンガ事業」の指揮を執っています。</p>
<p><img src="https://d1oxpwh25sez73.cloudfront.net/event_icons/ten.png" width="120"/></p>
<h3>四柳 剛さん / 株式会社TORICO</h3>
<p>開発本部 CTO スキマ・マンガ展のサービス立ち上げ、漫画全巻ドットコム・まんが王・ホーリンラブブックスの開発部門のマネージャーを担当。10年前はソーシャルゲームの開発者。</p>
<h1>注意事項</h1>
<ul>
<li>セクシュアルハラスメント、パワーハラスメント、ストーカー等の他者への迷惑行為を禁止します。</li>
<li>ネットワークビジネス等、その対象を問わず販売、勧誘、あっせん等を行うこと。また、宗教活動または政治活動を禁止します。</li>
<li>その他、イベントの趣旨・目的から逸脱した行為や、本来のイベント趣旨とは異なる行為があると判断した場合、退場いただいたうえで次回以降のイベント参加をお断りさせて頂きます。</li>
<li>当イベントへの参加は、事前登録をいただいた方のみに限定させていただきます。オンラインイベントの配信URLを事前登録のない方へ展開するのはご遠慮ください。</li>
<li>イベントの様子をブログや各種メディアにて発信するため、開催中に写真撮影をする場合がございます。ご了承お願い致します。</li>
</ul>
<h1>オフライン会場における新型コロナウイルス対策</h1>
<ul>
<li>感染拡大などやむをえない事情があった場合、オフラインでの開催は中止させて頂く場合があります。ご了承下さい。</li>
<li>発熱(37.5度以上)や風邪などの症状がある場合、参加をお控えください。</li>
<li>会場内ではマスクの着用をお願いいたします。大声での会話はお控えください。</li>
<li>会場施設の入口に消毒液が設置いたします。入場時に手の消毒をお願いいたします。</li>
</ul>
<h1>プライバシーポリシー</h1>
<p>アンケートなどで収集した個人情報に関しては、各社のプライバシーポリシーに則り、取り扱いいたします。</p>
<ul>
<li>
<p>株式会社メディアドゥ<br/><a href="https://mediado.jp/privacy-statement/" rel="nofollow" target="_blank">https://mediado.jp/privacy-statement/</a></p>
</li>
<li>
<p>ピクシブ株式会社<br/><a href="https://recruit.jobcan.jp/pixiv/policy" rel="nofollow" target="_blank">https://recruit.jobcan.jp/pixiv/policy</a></p>
</li>
<li>
<p>株式会社ブックウォーカー<br/><a href="https://www.bookwalker.co.jp/privacy_policy/main.html" rel="nofollow" target="_blank">https://www.bookwalker.co.jp/privacy_policy/main.html</a></p>
</li>
<li>
<p>株式会社TORICO<br/><a href="https://www.torico-corp.com/privacy/">https://www.torico-corp.com/privacy/</a></p>
</li>
</ul>
<p>参加希望者は、connpass のイベントのページから申し込みを行ってください。</p>
<p>› <a href="https://mediado-go.connpass.com/event/296676/" target="_blank">connpass の マンガTechTalk のページを見る</a></p>
<p></p>リカージョンをやってみての感想2023-09-29T11:55:28+00:002024-03-18T18:37:21+00:00百合川紗璃奈https://tech.torico-corp.com/blog/author/sarina.yurikawa/https://tech.torico-corp.com/blog/%E3%83%AA%E3%82%AB%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E3%82%92%E3%82%84%E3%81%A3%E3%81%A6%E3%81%BF%E3%81%A6%E3%81%AE%E6%84%9F%E6%83%B3/春から入社しました情報システム部の百合川です。<br/><br/>入社してからリカージョンという教材を使って勉強しています。<br/><br/>まずはリカージョンについて簡単に説明します。<br/>リカージョンは米国大学でよく用いられるコンピューターサイエンスのカリキュラムに基づいて作られた教材で、<br/>月額9000円弱(年契約にすると月額7000円になる)で<br/>Python、Javaなどのプログラミング言語だけでなく、DjangoやVueなどのフレームワーク、Unityなどのプラットフォーム、データベースまで幅広く学ぶことができます。<br/><br/>また補助機能として大きく3つあります。<br/><br/><strong>・AI Chatbot</strong><br/>AIが質問に答えてくれる機能<br/>リカージョンの内容に沿って答えてくれるので、問題で躓いた時に便利だった<br/><br/><strong>・AIコードレビュー機能</strong><br/>正解すればAIが自分の書いたコードをレビューしてくれる機能<br/>より良いコードを書く方法、コードをいかに短く書けるかや、名前の付け方、さらにはコメントの仕方までも指摘してくれる<br/><br/><strong>・ユーザー解答機能</strong><br/>ユーザーの解答を見ることができる機能<br/>自分のコードと比べてこうすればよかったという気づきや、自分と違う言語の解答も見ることができます。<br/><br/>またシェア機能、お気に入り機能などもあります。<br/><br/><span>その中で印象に残ったもの(面白かった問題)を紹介します。</span><br/><span>著作権にひっかかってしまうので、簡単にどういう問題かを説明して書こうと思います。<br/><br/><strong>中級 Pair of Cards midium</strong><br/><br/>これは個人的に楽しかったものです。<br/>先輩と一緒に取り組みました。<br/><br/>同じカードの枚数が多いプレイヤーが勝利、カードが同じ枚数だった場合は数字の大きさによって決まる、<br/>最終的に勝敗が決まらない場合はdrawにする、などいろいろな条件があります。<br/><br/>この問題はコードが長くなってしまい、気づいたら3000文字以上になってしまいました。<br/>ユーザーの解答を見たら、みんなそこそこ長くて安心しましたが、3000文字を超えてしまうと<br/>AIコードレビュー機能が使えないらしくショックでした。<br/>If文で回していたのでfor文を使って工夫すればもう少し短く書くことができたんじゃないかなあと思います。<br/><br/><strong>中級 FizzBuzz easy 中級 </strong><br/><br/>FizzBuzzに関しては元々有名な問題です。<br/>プログラマーなら誰しもが解いたことがあると思います。<br/>勉強会でもやりました、これを言われただけですぐにかけるようになればいいと言われました。<br/>私は条件分岐とforで書きましたが、再帰を使ったりなど色々な書き方があるみたいです。<br/><br/><strong>中級 オブジェクトの問題 easyからmidiumまで</strong><br/><br/>個人的に書いていて楽しい!と思ったのはこの辺でした。<br/>あまり難しくないのですが、コード自体は書いているうちに長くなるのですごく達成感がありました。<br/>楽しみながらスラスラと解けたので良かったです。<br/><br/><strong>上級 木構造の問題 easyからhardまで</strong><br/><br/>図を書き換えて頭の中で組み立てるのが苦手でした。<br/>二分木探索などはそもそも構造がわかっていないと解けないのでいろいろな記事を調べて自分なりに理解するしかありませんでした。<br/>ChatGPTにもたくさんお世話になりました、<br/>開発するとなると、どんなところで使うのかとか色々考えてしまいます。<br/>この項目に関しては、他以上に勉強が必要だと感じます。<br/><br/><strong>データベースの問題</strong><br/><br/>これも楽しかったです。<br/>もともとデーターベースが入っていて、問題に沿ってクエリを書くという感じでした。<br/>後半になってくると難しかったですが、全部ちゃんと読んでるとそこまで大変ではないという感じです。<br/>データベースに関しては私も少し齧った程度ですが初心者でも楽しく学べると思います。<br/>エイリアスを使用して可読性を向上させるというのは勉強になりました。<br/><br/>現在リカージョンでDjangoを触っていますが、<br/>端折ってかいているのでDjangoに関しては少しやりにくいような気がしたので<br/>別の教材を使用しています。<br/><br/>フレームワークに関しては少し取り組むのが難しいような気がしますが、<br/>全体的にはわかりやすく、楽しくやれています。<br/><br/>自分も早く開発の方に入れるように頑張りたいと思います。<br/></span>
<div></div>docker開発環境ですべてのログをFluentdに集約する2023-09-29T04:29:16+00:002024-03-19T05:46:29+00:00工藤淳https://tech.torico-corp.com/blog/author/kudou/https://tech.torico-corp.com/blog/docker%E9%96%8B%E7%99%BA%E7%92%B0%E5%A2%83%E3%81%A7%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E3%83%AD%E3%82%B0%E3%82%92fluentd%E3%81%AB%E9%9B%86%E7%B4%84%E3%81%99%E3%82%8B/<div>dockerの開発環境は便利なのですが、トラブルの発生時にコンテナごとにエラーを確認することになるので、そこは少し不便です。 なので、そこの利便性を上げるためにFluentdを使います。 今回はnginx + pfhp-fpm + fluentdの構成でログを集約してみます。</div>
<h4>ディレクトリ構造とファイルの配置の予定</h4>
<pre> my-app/
├─dokcer/
│ ├─nginx/
│ │ ├─nginx.conf
│ │ └─default.conf
│ ├─php-fpm/
│ │ ├─php-extends.ini
│ │ └─log.conf
│ └─fluentd/
│ └─fluent.conf
└─docker-compose.yml
</pre>
<h4>ファイルの作成</h4>
<h5>nginx.conf</h5>
<div>access_log、error_logを設定。 php-fpmとの接続を安定させるためにfastcgi_connect_timeout、fastcgi_read_timeout、fastcgi_send_timeoutを設定。</div>
<pre> user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log;
sendfile on;
#tcp_nopush on;
keepalive_timeout 300;
send_timeout 300;
fastcgi_connect_timeout 300;
fastcgi_read_timeout 300;
fastcgi_send_timeout 300;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
</pre>
<h5>default.conf</h5>
<div>host.access.log、host.error.logを設定。 9000番にきたらphp-fpmへつなげる。</div>
<pre> server {
listen 80;
listen [::]:80;
server_name ~^localhost$;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name ~^localhost$;
access_log /var/log/nginx/host.access.log;
error_log /var/log/nginx/host.error.log;
location / {
root /var/www/html/public;
index index.php index.html index.htm;
# リクエストされたファイルが存在しなければ、
# フロントコントローラーに内部リダイレクト
try_files $uri /index.php?$query_string;
}
error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
location ~ \.php$ {
root /var/www/html/public;
fastcgi_pass localhost$:9000;
fastcgi_index index.php;
# - 全てのリクエストをフロントコントローラーで実行
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
include fastcgi_params;
}
}
</pre>
<h5>php-extends.ini</h5>
<div>phpエラーログとタイムゾーンの設定。</div>
<pre> [date]
date.timezone = "Asia/Tokyo"
[php]
log_errors = On
error_log = /var/log/php-fpm/php.log
</pre>
<h5>log.conf</h5>
<div>php-fpmのエラーログを設定。</div>
<pre> ; The access log file
; Default: not set
access.log = /var/log/php-fpm/access.log
; Error log file
; If it's set to "syslog", log is sent to syslogd instead of being written
; into a local file.
; Note: the default prefix is /usr/local/var
; Default Value: log/php-fpm.log
error_log = /var/log/php-fpm/error.log
</pre>
<h5>fluent.conf</h5>
<div>fluentdでログを受け取る。<br/> そのまま出力する設定。<br/> docckerコンテナのログのタグと、matchを変更することで受け取るログを判別することもできる。</div>
<pre> <source>
@type forward
port 24224
</source>
<match *>
@type stdout
</match>
</pre>
<h4>docker-compose.ymlの作成</h4>
<div>コンテナのloggingでdriverにfluentdを指定することで、fluentdにログを送ることができます。</div>
<pre>version: '3'
services:
nginx:
build: nginx:1.23.3
container_name: nginx
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/local.conf:/etc/nginx/conf.d/default.conf
logging:
# ログ出力先にfluentdを指定
driver: "fluentd"
options:
# fluentdサーバー
fluentd-address: "localhost:24224"
# ログに付与するタグ
tag: "nginx"
fluentd-async-connect: "true"
depends_on:
- php-fpm
- fluent
php-fpm:
build: php:8.1.16-fpm
container_name: php-fpm
restart: always
volumes:
- ./php-fpm/php-extends.ini:/usr/local/etc/php/php-extends.ini
- ./php-fpm/log.conf:/usr/local/etc/php-fpm.d/log.conf
- ../:/var/www/html
logging:
# ログ出力先にfluentdを指定
driver: "fluentd"
options:
# fluentdサーバー
fluentd-address: "localhost:24224"
# ログに付与するタグ
tag: "php-fpm"
fluentd-async-connect: "true"
fluent:
image: "fluent/fluentd:v1.15-debian-1"
container_name: fluentd
ports:
- "24224:24224"
- "24224:24224/udp"
environment:
- 'FLUENTD_CONF=fluent.conf'
- 'TZ:Asia/Tokyo'
volumes:
- ./fluentd/config:/fluentd/etc/
</pre>
<h4>コンテナを起動する</h4>
<div>fluentdのログには、nginxとphp-fpmのログを取得する準備ができていることを確認できます。<br/> 起動したコンテナのログがfluentdで確認できるようになりました。</div>
<pre> 2023-09-25 12:16:25 2023-09-25 03:16:25 +0000 [info]: init supervisor logger path=nil rotate_age=nil rotate_size=nil
2023-09-25 12:16:25 2023-09-25 03:16:25 +0000 [info]: parsing config file is succeeded path="/fluentd/etc/fluent.conf"
2023-09-25 12:16:25 2023-09-25 03:16:25 +0000 [info]: gem 'fluentd' version '1.15.3'
2023-09-25 12:16:25 2023-09-25 03:16:25 +0000 [info]: using configuration file: <ROOT>
2023-09-25 12:16:25 <source>
2023-09-25 12:16:25 @type forward
2023-09-25 12:16:25 port 24224
2023-09-25 12:16:25 </source>
2023-09-25 12:16:25 <match nginx>
2023-09-25 12:16:25 @type stdout
2023-09-25 12:16:25 </match>
2023-09-25 12:16:25 <match php-fpm>
2023-09-25 12:16:25 @type stdout
2023-09-25 12:16:25 </match>
2023-09-25 12:16:25 </ROOT>
2023-09-25 12:16:25 2023-09-25 03:16:25 +0000 [info]: starting fluentd-1.15.3 pid=7 ruby="3.1.3"
2023-09-25 12:16:25 2023-09-25 03:16:25 +0000 [info]: spawn command to main: cmdline=["/usr/local/bin/ruby", "-Eascii-8bit:ascii-8bit", "/usr/local/bundle/bin/fluentd", "--config", "/fluentd/etc/fluent.conf", "--plugin", "/fluentd/plugins", "--under-supervisor"]
2023-09-25 12:16:25 2023-09-25 03:16:25 +0000 [info]: init supervisor logger path=nil rotate_age=nil rotate_size=nil
2023-09-25 12:16:25 2023-09-25 03:16:25 +0000 [info]: #0 init worker0 logger path=nil rotate_age=nil rotate_size=nil
2023-09-25 12:16:25 2023-09-25 03:16:25 +0000 [info]: adding match pattern="nginx" type="stdout"
2023-09-25 12:16:25 2023-09-25 03:16:25 +0000 [info]: adding match pattern="php-fpm" type="stdout"
2023-09-25 12:16:25 2023-09-25 03:16:25 +0000 [info]: adding source type="forward"
2023-09-25 12:16:25 2023-09-25 03:16:25 +0000 [info]: #0 starting fluentd worker pid=16 ppid=7 worker=0
2023-09-25 12:16:25 2023-09-25 03:16:25 +0000 [info]: #0 listening port port=24224 bind="0.0.0.0"
2023-09-25 12:16:25 2023-09-25 03:16:25 +0000 [info]: #0 fluentd worker is now running worker=0
</pre>情報システム部として2023年買ったもので良かったものを上げていくぞー [上半期編]2023-09-28T15:00:00+00:002024-03-19T03:15:17+00:00四斗邊貴博https://tech.torico-corp.com/blog/author/shitobe/https://tech.torico-corp.com/blog/buy-best-2023-under/<p class="p1">もう気がつけば9月も終わる!気づけばハロウィン、クリスマス、そして新年の足音が聞こえます。新年開けると歳を数えることになるので、苦悩が私には待ち受けていますが皆様いかがお過ごしでしょうか?</p>
<p class="p2">上半期に社屋の引っ越しがあり、普段購入しないものを会社で購入することができました。</p>
<p class="p1">それらを踏まえて上半期これは買って良かったものを列挙していきます。(なお順位はない)<br/><br/></p>
<h4 class="p1"><a href="https://www.amazon.co.jp/gp/product/B0B79Z183D/ref=ppx_yo_dt_b_asin_title_o07_s00?ie=UTF8&psc=1">Anker Make M5</a></h4>
<p class="p1"><img alt="" height="455" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/shitobe/ankermakem5.png" width="625"/></p>
<p class="p1">出力すごく早い。</p>
<p class="p1">小物はこれでちゃちゃっと作れる。(なお筆者は一度しか使っていない)</p>
<p class="p1">音もそこまで気にならないのですが、最大ノイズ量は50dBのようなので、エンクロージャーとか防音パネルで覆うは結構いるかも。</p>
<p class="p1">出力速度を25%にしたらそんなに気にならないなーという感想でした。(ただ、出力は遅くなる、遅くなるからと言って綺麗に出力されるというわけではない。)</p>
<p class="p2"></p>
<p class="p1">ただ、欠点があるとすれば、以下かな?</p>
<ul>
<li>最初の設定にはスマホが必要の模様。</li>
</ul>
<ul>
<li class="p1">3Dプリンタ側でIPアドレス固定できないこと(そんなのルーター側でやればいいじゃないと内なる自分がそう言います)</li>
</ul>
<p class="p2"></p>
<p class="p1">PS 3Dプリンター用にエンクロージャーは<a href="https://www.amazon.co.jp/gp/product/B09PRBXM4H/ref=ppx_yo_dt_b_asin_title_o06_s00?ie=UTF8&psc=1">こちら</a>を買いました。骨組み硬くて組み立てる際に力技が必要です。</p>
<p class="p1"></p>
<h4 class="p2"><a href="https://www.amazon.co.jp/gp/product/B09VGM78RR/ref=ppx_yo_dt_b_asin_title_o09_s00?ie=UTF8&psc=1"><br/>トラスコ プラ棚<br/></a></h4>
<p class="p1"><img alt="" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/shitobe/puratana.png" width="500"/></p>
<p class="p1">サーバー室用に買ったものですが、安価で軽くて簡単に組み立てれるのがすごく魅力的でした。</p>
<p class="p1">色は、黒が良かったのですが、amazonで見つけれなかった・・・。しぶしぶダークグリーン。</p>
<p class="p1">ちょうど引越し時期だったため、持ち運べれる棚が欲しいと思い購入した一品。</p>
<p class="p1"><br/>結果、最良でした。</p>
<p class="p1">1段ごと20kgまでであれば耐えるので、オリコンとか入れやすいです。<br/><img alt="" height="667" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/shitobe/trasco.jpeg" width="500"/></p>
<p class="p1">2棚密接に設置できれば結束バンドとかで耐震補強とかできちゃいます。移動させる時も結束バンド切ればいいので、大変いい。</p>
<h4><br/><a href="https://www.amazon.co.jp/dp/B01FHC0Q7W/ref=twister_B07D9H9V1P?_encoding=UTF8&th=1">貫通式 LANコネクタ</a></h4>
<p class="p1"><img alt="" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/shitobe/lan-conect.png" width="500"/><br/>みなさんLANケーブル自作しないですか?</p>
<p class="p1">TORICOでは自作ケーブルを作ることは多いです。</p>
<p class="p1">簡単な配線とかは自分たちで配線工事はするぐらいです。</p>
<p class="p1">で、今までコネクタをカシメする際に、非貫通なコネクタを活用していたのですが、これがLANケーブルの8本線のうち、どれか一本が奥まで届かず通線できていないことが多かったですが、この貫通式にすると、まず線が確実に金具の位置まで届くのと、かしめる前に線の確認ができるという誤りが確実になくなる。</p>
<p class="p1">CAT5eのケーブルは楽勝にできるようになるのと、CAT6aのケーブルも初心者であれば難なくできるようになります。(相変わらずCAT6aの間の十字芯硬すぎる)</p>
<p class="p2"></p>
<h4><a href="https://www.amazon.co.jp/SwitchBot-%E3%83%97%E3%83%A9%E3%82%B0%E3%83%9F%E3%83%8B-%E3%82%B9%E3%83%9E%E3%83%BC%E3%83%88%E3%83%97%E3%83%A9%E3%82%B0-%E3%82%A2%E3%83%AC%E3%82%AF%E3%82%B5-%E3%82%BF%E3%82%A4%E3%83%9E%E3%83%BC%E4%BB%98%E3%81%8D/dp/B09XMZQMBP/ref=sr_1_1_sspa?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=3SREDLIU1SJK8&keywords=switchbot%E3%81%AE%E3%82%B9%E3%83%9E%E3%83%BC%E3%83%88%E3%83%97%E3%83%A9%E3%82%B0&qid=1695888805&sprefix=switchbot%E3%81%AE%E3%82%B9%E3%83%9E%E3%83%BC%E3%83%88%E3%83%97%E3%83%A9%E3%82%B0%2Caps%2C193&sr=8-1-spons&sp_csd=d2lkZ2V0TmFtZT1zcF9hdGY&th=1"><br/>switchbotのスマートプラグ</a></h4>
<p class="p1"><img alt="" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/shitobe/swichbot-smart-plug.png" width="500"/><br/>Swichbotのスマートプラグはいいぞ・・・。</p>
<p class="p1">何がいいって、消費電力測定してくれるんですよ。さらにこいつは1時間あたりの消費電力(kWh)とかも出してくれるんですよ。</p>
<p class="p1">通常は消費<span>電力測定</span>機を外につければいいじゃないと思うんですが、それが一台にできるのって便利すぎひん?</p>
<p class="p1">スマートプラグであればTP-linkとか1000円ぐらいで買えてしまいますが、それよりももう1夏目(知ってる人は30代後半以上)追加で Wチェッカーできる方がコスパと利便性いいなと思います。</p>
<p class="p1">オンプレサーバーとか電気代とか予測できちゃうし、時間帯ごと電力値表示されるのでPCの計算量と電力値で比較計測しやすくなるからありがたい。</p>
<p class="p2"><img alt="" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/shitobe/img_0440.jpg" width="500"/></p>
<h4><a href="https://www.amazon.co.jp/%E3%83%A4%E3%83%9E%E3%83%8F-YAMAHA-WLX212-W-%E3%83%9B%E3%83%AF%E3%82%A4%E3%83%88/dp/B0C56PH8CK/ref=sr_1_2?keywords=YAMAHA+wlx212&qid=1695888863&sr=8-2"><span><br/>YAMAHA</span> wlx212</a></h4>
<p class="p1"><img alt="" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/shitobe/wlx212.png" width="500"/><br/>さいごにYAMAHA製のAPです。</p>
<p class="p1">現状上位のwlx222が発売されていますが、価格半分ぐらいで買えるwlx212が非常にコスパが良いです。</p>
<p class="p1">TORICOとして必要な機能としては、APクラスターがあり、かつSSIDをそれぞれAPに振り分けれるのがベストだったのですが、最近の2022年のアップデートだったか?の対応により、SSIDをそれぞれAPに振り分けれるようになっています。</p>
<p class="p3"><span class="s1">wlx222</span>とwlx<span class="s1">212</span>の違いは、<span class="s1">wifi6</span>に対応したということに重みを置いていると思うのですが、この機能はまだまだメリットをあまり感じなくて、<span class="s1">wifi5</span>が使えれば全然オフィス使いでは支障がないです。</p>
<p class="p1">否定姫ほど否定する気はないですが、<span class="s1">wlx222</span>の存在を否定するわぁ~。<br/>ちなみにwlx212 とwlx222を外側では判断できないので注意です。<br/><br/>以上です。<br/>これらは買って後悔しなかったものなので、みなさんが購入検討している場合はご参考ください。<br/>それでは、また下期でお会いしましょう。</p>情報セキュリティの脅威は身近に潜んでいる(ネット初心者)2023-09-28T03:00:00+00:002024-03-18T19:07:51+00:00渡辺美由紀https://tech.torico-corp.com/blog/author/m.watanabe/https://tech.torico-corp.com/blog/Phishing/<h3><span>情報セキュリティの脅威は身近に潜んでいる</span></h3>
<p><span> 情報セキュリティの脅威と聴くと、国や企業に向けてインターネットに仕掛けたサイバー攻撃などを連想される人もいると思います。<br/>実は、個人に向けた脅威もあります。<br/></span> それは、偽サイトを用いた第三者による個人情報等の<span>詐取</span>、SNS乗っ取り、クレジットカードやスマホ決済の不正利用、詐欺などの犯罪、またインターネットを利用した個人への誹謗中傷デマなどの精神的被害が大きい犯罪なども情報セキュリティの脅威に当たります。<br/> 毎年、<span>IPA情報処理推進機構さんが、ランキングを作成しているので確認すると良いでしょう。<br/>その中の「フィッシングによる個人情報等の詐取」はとは何か以下を記載します。</span></p>
<table border="2" height="276" style="height: 276px;" width="517"><caption></caption>
<tbody>
<tr>
<td style="text-align: center;">順位</td>
<td><span> セキュリティの脅威(個人)</span></td>
<td style="text-align: center;">前年順位</td>
</tr>
<tr>
<td style="text-align: center;"> 1位 </td>
<td> フィッシングによる個人情報等の詐取</td>
<td style="text-align: center;">1位</td>
</tr>
<tr>
<td style="text-align: center;"> 2位 </td>
<td> ネット上の誹謗・中傷・デマ</td>
<td style="text-align: center;">2位</td>
</tr>
<tr>
<td style="text-align: center;"> 3位 </td>
<td><span> メールやSMS等を使った</span><span>脅迫・詐欺の手口による金銭要求</span></td>
<td style="text-align: center;">3<span>位</span></td>
</tr>
<tr>
<td style="text-align: center;"> 4位 </td>
<td> クレジットカード情報の不正利用</td>
<td style="text-align: center;">4<span>位</span></td>
</tr>
<tr>
<td style="text-align: center;"> 5位 </td>
<td> スマホ決済の不正利用</td>
<td style="text-align: center;">5<span>位</span></td>
</tr>
<tr>
<td style="text-align: center;"> 6位 </td>
<td><span> 不正アプリによる</span><span>スマートフォン利用者への被害</span></td>
<td style="text-align: center;">7<span>位</span></td>
</tr>
<tr>
<td style="text-align: center;"> 7位 </td>
<td><span> 偽警告によるインターネット詐欺</span></td>
<td style="text-align: center;">6<span>位</span></td>
</tr>
<tr>
<td style="text-align: center;"> 8位 </td>
<td><span> インターネット上のサービスからの</span><span>個人情報の窃取</span></td>
<td style="text-align: center;">8<span>位</span></td>
</tr>
<tr>
<td style="text-align: center;"> 9位 </td>
<td><span> インターネット上のサービスへの</span><span>不正ログイン</span></td>
<td style="text-align: center;">10<span>位</span></td>
</tr>
<tr>
<td style="text-align: center;">10位</td>
<td><span> ワンクリック請求等の</span><span>不正請求による金銭被害</span></td>
<td style="text-align: center;">圏外</td>
</tr>
</tbody>
</table>
<p>出典:IPA情報処理推進機構 情報セキュリティ10大脅威 2023 <a href="https://www.ipa.go.jp/security/10threats/10threats2023.html" target="_blank">https://www.ipa.go.jp/security/10threats/10threats2023.html</a></p>
<p></p>
<h2>「フィッシングによる個人情報等の詐取」</h2>
<h3>フィッシングとは</h3>
<p> フィッシングとは、実在する機関や企業また実際に利用した事のあるサイトを騙ったメールやSMS(ショートメールサービス)などを送り、偽のサイトに誘導して、IDやパスワード、また個人情報など入力させ<span>窃取</span>する手法です。<br/> <span>昨今</span>は、スマートフォンの普及、銀行やショッピング、行政など、多くの分野でネット経由で登録・申し込みをする機会が増えてきました。それに伴い、有名な企業や行政などを語る不審なメールに不信感をもらずアクセスしてしまう人が多くおり「フィッシングによる個人情報等の詐取」の被害が増えています。</p>
<h3><br/>フィッシングと気づかずアクセスや登録してしまうと…</h3>
<ul>
<li>盗まれた個人情報などを販売される</li>
<li><span>盗まれた</span>個人情報で何かしらの口座を作成され、犯罪窓口に利用される</li>
<li><span>盗まれた</span>IDやパスワードを使ってオンラインショッピングサイト等にログインし購入され、その金額の請求される</li>
<li><span>盗まれた</span>IDやパスワードが他サイト兼用であれば、そのサイトまで悪用される</li>
</ul>
<h3>システムを分からなくても個人でもできる予防</h3>
<strong>メールソフトやキャリア(docomo等の携帯会社)のセキュリティチェックを利用する</strong><br/> メールを送受信するメールソフトやサーバーには、迷惑メールフォルダ機能やセキュリティチェックが備えられています。<br/> キャリア側でも、スマートフォンに送る前に遮断してくれるメール受信設定などもあります。<br/><strong><br/>利用のないサイトからのメールは開封しない<br/></strong> まったく覚えのないサイトや企業からのメールは開封しない方が良いです。もし開封しても覚えがなければ、迷惑メールとして登録しておきましょう。<br/> 登録する事によって、類似するようなメールも迷惑メールフォルダに入ります。<br/> ただし、必要なメールも迷惑メールに入る事があるので、見つけられない場合は迷惑メールフォルダを確認してみましょう。<br/><strong><br/>利用サイトや行政からでも、本文のURLリンクを安易にクリックしない</strong><br/> 利用サイトや行政からでも、ID、パスワードやその他の個人情報等の要求であれば怪しいと疑うことが大切です。<br/> 本文のリンクをクリックせずに、自分のブックマークやGoogleなどの検索エンジンから利用サイトや行政サイトへ移動しメールについて確認しましょう。<br/><br/><strong>「有効期限が切れます」などが含まれる文章を読んだ時ほど落ち着こう<br/><span> </span></strong>「有効期限が切れます」「すぐに電話をください」「アクセスすると延長できます」など焦らせる文章が含まれるメールは、思考を混乱させ、焦らせる心理作戦です。<br/> 本文にある電話番号にはかけない、リンクをクリックしない。利用サイトであれば、<span>自分のブックマークやGoogleなどの検索エンジンからサイトへ移動する。</span>また不安であれば、他の人も同じようなメールが届いていないかGoogleなどの検索エンジンから探してみるのもよいでしょう。<br/><br/><strong>「当選されました」などが含まれる文章があった時は本当に抽選に申し込んだか確認しよう<br/></strong><strong><span> </span></strong><span>「当選されました」などの興奮させる文章が含まれるメールは、平常心を無くすようにする心理作戦です。</span><br/><span> まずは、送られたサイトや企業に抽選を申し込んだことがあるか、考えてみましょう。もし心当たりなく、家族や友人もそのキャンペーンを知らない場合は詐欺の可能性があります。<br/> また記憶がある場合でも、サイトのURLなどが正しいか、Googleなどの検索エンジンなどで調べてみましょう。</span><br/>
<h2>まとめ</h2>
<p>日常生活で、意識せずに確認しているメールにも、少し意識をもって見てみると、脅威が潜んでいることがある事が分かります。<br/>個人の場合の予防は、一人一人が意識して、メールに対して焦らずに確認をしていくことが、とっても重要な事です。<br/>友人や家庭でも情報セキュリティの脅威について、たまに話題を出しておくと、意識が高まり、メールを読むときにも意識するようになるでしょう。</p>django admin画面に複数選択可能なカスタムリストフィルターを追加する2023-09-27T09:09:45+00:002024-03-19T03:14:23+00:00工藤淳https://tech.torico-corp.com/blog/author/kudou/https://tech.torico-corp.com/blog/django-admin%E7%94%BB%E9%9D%A2%E3%81%AB%E8%A4%87%E6%95%B0%E9%81%B8%E6%8A%9E%E5%8F%AF%E8%83%BD%E3%81%AA%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E3%83%AA%E3%82%B9%E3%83%88%E3%83%95%E3%82%A3%E3%83%AB%E3%82%BF%E3%83%BC%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B/<div>django admin画面のフィルター機能に不満があるので、 複数選択可能なListFilterを作成します。</div>
<br/>
<h4>modelを準備する</h4>
<div>以下のようなmodelを準備します。<br/> 商品マスタ product<br/> 作家マスタ author<br/> 出版社マスタ publisher</div>
<pre>class Product(models.Model):
product_id = models.BigAutoField(
primary_key=True,
verbose_name='商品マスタID'
)
product_name = models.TextField(
verbose_name='商品名'
)
product_price = models.IntegerField(
verbose_name='商品価格',
)
author = models.ForeignKey(
Author,
on_delete=models.PROTECT
)
publisher = models.ForeignKey(
Publisher,
on_delete=models.PROTECT
)
class Meta:
managed = False
db_table = 'product'
verbose_name = '商品マスタ'
verbose_name_plural = '商品マスタ'
def __str__(self):
return self.product_name
class Publisher(models.Model):
publisher_id = models.BigAutoField(
primary_key=True,
verbose_name='出版社マスタID'
)
publisher_name = models.TextField(
verbose_name='出版社名'
)
class Meta:
managed = False
db_table = 'publisher'
verbose_name = '出版社マスタ'
verbose_name_plural = '出版社マスタ'
def __str__(self):
return self.publisher_name
class Author(models.Model):
author_id = models.BigAutoField(
primary_key=True,
verbose_name='作家マスタID'
)
author_name = models.TextField(
verbose_name='作家名'
)
class Meta:
managed = False
db_table = 'author'
verbose_name = '作家マスタ'
verbose_name_plural = '作家マスタ'
def __str__(self):
return self.author_name
</pre>
<br/>
<h4>商品マスタを管理画面で呼び出せるようにadmin.pyに以下のように記述します</h4>
<pre>@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = (
'product_id',
'product_name',
'product_price',
'author',
'publisher',
)
list_display_links = (
'product_name',
)
list_filter = (
'author',
'publisher',
)
search_fields = (
'product_name',
)
ordering = (
'product_id',
)
</pre>
<div>これで商品マスタ product が表示できるようになりました。</div>
<br/>
<h4>新たにコミックや小説などのカテゴリを記録するためのカテゴリマスタを作成します</h4>
<pre>class Category(models.Model):
category_id = models.BigAutoField(
primary_key=True,
verbose_name='カテゴリマスタID'
)
category_name = models.TextField(
verbose_name='カテゴリ名'
)
class Meta:
managed = False
db_table = 'category'
verbose_name = 'カテゴリマスタ'
verbose_name_plural = 'カテゴリマスタ'
def __str__(self):
return self.category_name
</pre>
<div>商品マスタ product にも外部キーでカテゴリマスタを設定します。</div>
<pre> category = models.ForeignKey(
Category,
on_delete=models.PROTECT
)
</pre>
<div>これでカテゴリも表示できるようになりました。<br/> admin.pyに追加すれが表示できるのですが、カテゴリは複数選択できるようにしたいとおもいました。<br/> そこで複数選択可能なカスタムリストフィルターを作成します。<br/>
<h4>複数選択可能なカスタムリストフィルター作成する</h4>
<div>admin.pyに ListFilter を継承した複数選択可能なカスタムリストフィルターを作成します。</div>
<pre> class MultiSelectListFilter(admin.ListFilter):
"""
複数選択可能なリストフィルター
admin の ListFilter を継承しています
"""
title = '' # 項目として表示される名称
model = '' # 使用するmodel名
parameter_name = '' # リクエストのパラメータ名
field_name = '' # 使用するmodelのフィールド名
def __init__(self, request, params, model, model_admin):
# ListFilter を継承
super().__init__(request, params, model, model_admin)
# 複数選択にしたいのでリクエスト値を in にします
self.lookup_kwarg = '%s__in' % self.parameter_name
# ここらへんは SimpleListFilter と同じ
if self.lookup_kwarg in params:
value = params.pop(self.lookup_kwarg)
self.used_parameters[self.lookup_kwarg] = value
lookup_choices = self.lookups(request, model_admin)
if lookup_choices is None:
lookup_choices = ()
self.lookup_choices = list(lookup_choices)
def has_output(self):
# SimpleListFilter と同じ
return len(self.lookup_choices) > 0
def value(self):
# SimpleListFilter と同じ
return self.used_parameters.get(self.lookup_kwarg)
def expected_parameters(self):
# SimpleListFilter と同じ
return [self.lookup_kwarg]
def lookups(self, request, model_admin):
ids = {}
if self.value():
# リクエスト値がある場合、選択済みの値を除外したtupleを返す
for v in self.model.objects.all():
current = self.value().split(',')
parameter_name = str(getattr(v, self.parameter_name))
if parameter_name in current:
current.remove(parameter_name)
else:
current.append(parameter_name)
str_ids = ','.join(list(current))
ids.update({str_ids: getattr(v, self.field_name)})
return tuple(ids.items())
else:
# デフォルトは全件表示。パラメータ名、フィールド名のtupleを返す
return self.model.objects.all().values_list(
self.parameter_name,
self.field_name
)
def choices(self, changelist):
# 全て を表示
yield {
'selected': self.value() is None,
'query_string': changelist.get_query_string(
remove=[self.parameter_name]
),
'display': _('All'),
}
# 選択済みの値はselectedで表示する。この判定のためにis_match関数を作成している
for lookup, title in self.lookup_choices:
yield {
'selected': self.is_match(lookup, self.value()),
'query_string': changelist.get_query_string(
{self.lookup_kwarg: lookup}
),
'display': title,
}
def queryset(self, request, queryset):
if self.value():
# 選択されたデータで絞り込んで表示
query = {
self.lookup_kwarg: self.value().split(','),
}
return queryset.filter(**query)
else:
# デフォルトの表示
return queryset.all()
@staticmethod
def is_match(lookup: str, value: str):
"""
選択済みの値とリクエストの値が一致しているか判定する
:param lookup: 選択済みの値
:param value: リクエストの値
:return:
"""
if not lookup:
return True
if not value:
return False
if all(v in lookup.split(',') for v in value.split(',')):
return False
else:
return True
</pre>
<div>admin.py に カテゴリマスタとカスタムリストフィルターでの表示を記載します。</div>
<pre>@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
class CategoryListFilter(MultiSelectListFilter):
"""
カテゴリマスタのカスタムフィルターの設定
"""
title = 'カテゴリ'
model = Category
parameter_name = 'category_id'
field_name = 'category_name'
list_display = (
'product_id',
'product_name',
'product_price',
'author',
'publisher',
'category', # カテゴリマスタの追加
)
list_display_links = (
'product_name',
)
list_filter = (
'author',
'publisher',
CategoryListFilter, # カテゴリマスタのリストフィルターの追加
)
search_fields = (
'product_name',
)
ordering = (
'product_id',
)
</pre>
<div>商品マスタの表示から「小説」「文庫」を選択</div>
<div style="display: block; margin-left: auto; margin-right: auto;" width="512px"><img alt="" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/kudou/screenshot-2023.09.29-17_02_17.png"/></div>
<div>これで複数選択の絞り込みができるようになりました。</div>
<div style="display: block; margin-left: auto; margin-right: auto;" width="512px"><img alt="" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/kudou/screenshot-2023.09.29-17_02_34.png"/></div>
<div>SimpleListFilter が便利なのですが、ちょっと痒いところに手が届かなかったので作成しました。</div>
</div>サイバー攻撃とは2023-09-21T09:00:00+00:002024-03-18T21:50:30+00:00渡辺美由紀https://tech.torico-corp.com/blog/author/m.watanabe/https://tech.torico-corp.com/blog/cyber_attack/<h3>サイバー攻撃って何?</h3>
<p> サイバー攻撃とは、あるコンピュータシステムやネットワーク、電子機器などに対し、正しいアクセス権限を持っていない悪意のある者が不正な手段で働きかけ、機能不全や停止に追い込んだり、データの改ざんや詐取、遠隔操作などを行うことです。<br/> 大きく分けて、特定の組織や集団、個人を狙ったものと、不特定多数を無差別に攻撃するものの2種類があります。また、サイバーテロ(cyberterrorism)という政治的な示威行為として行われるもの、サイバー戦争(cyberwarfare)という国家間などで行われるものなどもあります。</p>
<br/>
<h5>サイバー攻撃の一例</h5>
<span>・Webサーバに侵入してサイトの内容を改竄<br/> ・情報システムに侵入して機密情報や個人情報を盗む<br/> ・大量のアクセスを集中させてサーバや回線を機能不全に追い込む(DoS攻撃/DDoS攻撃)<br/> ・アクセス権限を不正に取得して本人になりすましてシステムを操作する<br/> ・システムを使用不能にして回復手段の提供に身代金を要求(ランサムウェア)<br/> ・標的システムに直接働きかけて攻撃を実行する<br/>・コンピュータウイルスやトロイの木馬などのマルウェアを感染させる<br/> ・送り込んだマルウェアに外部から指令を送って遠隔操作する</span>
<h3>マルウェアとは</h3>
<p> コンピューターやその利用者に被害をもたらすことを目的とした悪意のあるソフトウェアをマルウェアといいます。もともとコンピューターウイルスやワームなどと呼ばれていましたが、悪意のあるソフトウェアを総称する用語としてマルウェアが広まりました。</p>
<br/>
<h5>どのようなものがあるか</h5>
・プログラムやファイルの一部を書き換えて自己複製するコンピューターウイルス<br/> ・独立したプログラムとして実行され、ネットワークなどを介してほかのコンピューターに拡散するワーム<br/> ・攻撃者の命令に従ってDDoS攻撃や迷惑メールの送信などを行うボット<br/> ・暗号化などによってファイルを利用不能な状態にし、元に戻すことと引き換えに金銭を要求するランサムウェア<br/>
<h3>ウイルス対策ソフト</h3>
<p> これらのマルウェアを検知・感防止・排除するためのソフトウェアとして広く使われているのがウイルス対策ソフトです。<br/> マルウェアを検知する方法としては、マルウェアの情報をパターンファイルなどと呼ばれるデータベースに登録し、その内容と検査対象を比較するパターンマッチングと呼ばれる手法が一般的に使われています。また、プログラムの振る舞いを監視し、その結果からマルウェアかどうかを判断する手法もあります。<br/> 企業や個人のコンピューターやスマートフォン、タブレットなどインターネットに接続できる端末にこのウイルス対策ソフトを導入することは良い対策だといえるでしょう。</p>
<h3>マルウェアは進化する</h3>
<p> しかしながら、膨大な数のマルウェアが日々生成・進化しています。そのため、ウイルス対策ソフトだけでは十分に保護できない状況になっています。<br/> インターネットを経由したサービスを行っている企業あるいか個人は、ウイルス対策ソフトだけに頼るのではなく、複数のセキュリティ対策を組み合わせることが重要であるといえます<br/> またマルウェアの多くは、OSやアプリケーションの脆弱性を利用して感染します。そのため、開発者は、システムの脆弱性を早期に発見し、解消するための修正プログラムおよびパッチの適用は極めて重要な課題となっています。</p>
<h3>まとめ</h3>
<p> 昨今サイバー攻撃は企業のみならず、個人でも身近な犯罪としてニュースでも良く聞くようになりました。また、テレビ番組でもピックアップされるようになりました。<br/> わたしたちが出来る事は、サイバー攻撃の事前予防はもちろんですが、怪しい広告バナーやメールのURLをを押さない、契約ある業者からもメール文章に不安があればアクセスしないなど、自身で注意することが必要です。<br/> また、サイバー攻撃の被害にあった場合は、警察機関に相談することが必要です。次に同じ犯罪を防ぐために情報共有が求められます。</p>
<p></p>
<p>サイバー攻撃対策|警察庁Webサイト <br/><a href="https://www.npa.go.jp/bureau/security/cyber/index.html" target="_blank" title="サイバー攻撃対策">https://www.npa.go.jp/bureau/security/cyber/index.html</a></p>django admin画面に簡単にファイルの入力・出力を追加する2023-09-19T03:26:10+00:002024-03-19T03:15:42+00:00工藤淳https://tech.torico-corp.com/blog/author/kudou/https://tech.torico-corp.com/blog/django-admin%E7%94%BB%E9%9D%A2%E3%81%AB%E7%B0%A1%E5%8D%98%E3%81%AB%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E5%85%A5%E5%8A%9B%E5%87%BA%E5%8A%9B%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B/<div>django admin画面はmodelのデータ管理に非常に便利な機能です。<br/> csvなどのファイルでのデータの追加、出力ができるとさらに便利です。<br/> <code>django-import-export</code>プラグインを導入することでこの機能を簡単に導入することができます。</div>
<br/>
<h4>django-import-exportプラグインを追加する</h4>
<pre>pip install django-import-export
</pre>
<br/>
<h4>modelを準備する</h4>
<div>以下のようなmodelを準備します。<br/> 商品マスタ product<br/> 作家マスタ author<br/> 出版社マスタ publisher</div>
<pre>class Product(models.Model):
product_id = models.BigAutoField(
primary_key=True,
verbose_name='商品マスタID'
)
product_name = models.TextField(
verbose_name='商品名'
)
product_price = models.IntegerField(
verbose_name='商品価格',
)
author = models.ForeignKey(
Author,
on_delete=models.DO_NOTHING
)
publisher = models.ForeignKey(
Publisher,
on_delete=models.DO_NOTHING
)
class Meta:
managed = False
db_table = 'product'
verbose_name = '商品マスタ'
verbose_name_plural = '商品マスタ'
def __str__(self):
return self.product_name
class Publisher(models.Model):
publisher_id = models.BigAutoField(
primary_key=True,
verbose_name='出版社マスタID'
)
publisher_name = models.TextField(
verbose_name='出版社名'
)
class Meta:
managed = False
db_table = 'publisher'
verbose_name = '出版社マスタ'
verbose_name_plural = '出版社マスタ'
def __str__(self):
return self.publisher_name
class Author(models.Model):
author_id = models.BigAutoField(
primary_key=True,
verbose_name='作家マスタID'
)
author_name = models.TextField(
verbose_name='作家名'
)
class Meta:
managed = False
db_table = 'author'
verbose_name = '作家マスタ'
verbose_name_plural = '作家マスタ'
def __str__(self):
return self.author_name
</pre>
<br/>
<h4>商品マスタを管理画面で呼び出せるようにadmin.pyに以下のように記述します</h4>
<pre>@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = (
'product_id',
'product_name',
'product_price',
'author',
'publisher',
)
list_display_links = (
'product_name',
)
list_filter = (
'author',
'publisher',
)
search_fields = (
'product_name',
)
ordering = (
'product_id',
)
</pre>
<div>これで商品マスタ product が表示できるようになりました。</div>
<br/>
<h4>ファイルのインポート・エクスポート機能を追加する</h4>
<div>admin.pyにプラグインの読み込みを記述します</div>
<pre>from manga_server_django.admin.import_export import CommonImportExportSetting
</pre>
<div>ファイルのインポート・エクスポート機能を追加するためにadmin.pyにresourcesを追加します。<br/> 出力するmodelの項目名を指定します。<br/> 項目名の日本語名への変換や、取り込み時に外部キーで対応する項目名の指定などもできます。</div>
<pre>class ProductResource(resources.ModelResource):
# modelの項目名を指定。フィールド名・日本語表記の変換などもここで行う
product_id = Field(
attribute='product_id',
column_name='商品マスタID'
)
product_name = Field(
attribute='product_name',
column_name='商品名'
)
product_price = Field(
attribute='product_price',
column_name='商品価格'
)
# 外部キーのフィールドの場合はForeignKeyWidgetを記述する
publisher_name = Field(
attribute='publisher_name',
column_name='出版社名'
widget=ForeignKeyWidget(Publisher, 'publisher_name'),
)
author_name = Field(
attribute='author_name',
column_name='作者名'
widget=ForeignKeyWidget(Author, 'author_name'),
)
class Meta:
model = Product
# 出力設定
# ヘッダーに出力する文字列
headers = (
'商品マスタID',
'商品名',
'商品価格',
'出版社名',
'作者名',
)
# 出力するフィールド指定
fields = (
'product_id',
'product_name',
'product_price',
'publisher_name',
'author_name',
)
# 出力するフィールドの順番指定
export_order = (
'product_id',
'product_name',
'product_price',
'publisher_name',
'author_name',
)
# 入力設定
# 入力の主キーとなる項目の指定
import_id_fields = ('product_id',)
# 入力するフィールドの指定
import_order = (
'product_id',
'product_name',
'product_price',
'publisher_name',
'author_name',
)
# 更新のない行はスキップする
skip_unchanged = True
# 結果表示はスキップしない
report_skipped = False
</pre>
<div>ProductAdminに<code>ImportExportMixin</code>を追加。<br/> <code>resource_class</code>に先ほど作成した<code>ProductResource</code>を指定します。</div>
<pre>@admin.register(Product)
class ProductAdmin(ImportExportMixin, admin.ModelAdmin):
# 省略
resource_class = ProductResource
# 入力・出力するフォーマット。下記ではcsvとExcelファイルにしています。他にもtsv、YAML、XMLなども指定できます。
formats = [
base_formats.CSV,
base_formats.XLS,
base_formats.XLSX,
]
# 出力する文字コード指定
to_encoding = 'CP932'
# 入力する文字コード指定
from_encoding = 'CP932'
</pre>
<div>これで管理画面から商品マスタのインポート・エクスポートができるようになりました。</div>
<br/>
<h5>admin画面 上部の表示</h5>
<div style="display: block; margin-left: auto; margin-right: auto;" width="512px"><img alt="" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/kudou/screenshot-torico.manga-server.com-2023.09.19-13_14_35.png"/></div>
<h5>エクスポート画面</h5>
<div style="display: block; margin-left: auto; margin-right: auto;" width="512px"><img alt="" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/kudou/screenshot-torico.manga-server.com-2023.09.19-13_27_21.png"/></div>
<h5>インポート画面</h5>
<div style="display: block; margin-left: auto; margin-right: auto;" width="512px"><img alt="" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/kudou/screenshot-torico.manga-server.com-2023.09.19-13_30_30.png"/></div>
<br/>
<div>ちょっとしたプラグインの追加でdjango admin画面で大量のデータをより使いやすくできるので非常に便利です。</div>ITパスポートにもよく出るCIAって?(3要素から7要素へ)2023-09-13T03:00:00+00:002024-03-19T03:37:50+00:00渡辺美由紀https://tech.torico-corp.com/blog/author/m.watanabe/https://tech.torico-corp.com/blog/CIA/<h2>「情報セキュリティ」とは何か</h2>
<span> 企業や個人の情報資産を安全に取り扱うための3つの要素CIA(機密性、完全性、可用性)を確保することです。</span><br/>この要素を確保する事により、データの流出、改ざん、破損、消失などの脅威から企業や個人のデータを守ることができます。<br/>
<h3><span>「機密性」とは何か</span></h3>
<p><span>機密性(Confidentiality)とは情報が許可を受けた者にだけアクセスができるようにし、権限のない者から情報を守ることをいいます。</span></p>
<h5>「機密性」を確保しないと…</h5>
<p><span> 重要な情報(個人情報・パスワード・クレジットカード番号など)が漏えいし、悪意のある人により、その情報を悪用されてしまう恐れがあります。また、ECサイトではとても大切なユーザー情報もあり、とても重要視しています。</span></p>
<h5>どのような対策を取るか</h5>
<p><span> ・ログインパスワードなどを用いて関係のない第三者のアクセス拒否する<br/> ・サイトからの悪意ある人の連続不正アクセスを防ぐ<br/> ・システム内部データへの不正アクセスを防ぐ </span></p>
<h5>個人でも出来ること</h5>
<p><span> ・鍵や暗証番号による自宅の施錠<br/> ・スマートフォン・PCなどの端末にロック解除のためのパスワード<br/> ・顔認証などを登録<br/> ・登録しているサイトや銀行カードのパスワードを定期的に変更 </span></p>
<h3><span>「完全性」とは何か</span></h3>
<p><span> 完全性(Integrity)とは、データを最新かつ一貫性をもった正しい状態のまま、維持することをいいます。</span></p>
<h5>「完全性」を確保できないと…</h5>
<p><span> 悪意のある人がデータを改ざんできてしまうと、データが正しくなくなり、信頼性がなくなる。<br/>またデータが古いままであると、ユーザーが正しく判断できず、信用性がなくなる。</span></p>
<h5>どのような対策を取るか</h5>
<p><span> ・データの改ざんをさせないために機密性の確保<br/> ・通信の暗号化やデジタル署名を用いること<br/> ・バックアップでの完全な復元<br/> ・データ編集時のダブルチェックで人為的ミスを未然に防止<br/> ・ログを記録しておくことにより対策を整える<br/> </span></p>
<h3><span>「可用性」とは何か</span></h3>
<p><span> 可用性(Availability)とは、システムを障害(機器やパーツの故障・災害・アクシデントなど)で停止させることなく稼働し続けること、またはその指標のことをいいます。</span></p>
<h5>「可用性」を確保しないと…</h5>
<p><span> 長期間のサービスが提供できず、停止期間にユーザーが離れる。また、アクシデントで損失した情報を取り戻せず再稼働が容易にできない。</span></p>
<h5>どのような対策を取るか</h5>
<p><span> ・予備サーバーなどで負荷分散をさせる<br/> ・レプリケーションを作り、元のデータコピーを継続的にする<br/> ・外部の脅威からのサーバ―負荷を回避するためにIP制限などを用いる<br/> </span></p>
<h3>新たに加わった4つの要素</h3>
<p><span>上記の3つが従来からあるCIAの3要素でした。そちらに新たに加わった4つの要素の<span>真正性、</span><span>信頼性、</span><span>責任追跡、</span><span>性否認防止</span><span>のこれらは、誰が情報へのアクションをしたかを確認できるようにすることや、システムが確実に目的の動作をすること、情報を後から否定されない状況を作る事で情報セキュリティを確保するものです。</span></span></p>
<h3><span>「真正性」とは何か</span></h3>
<p><span> 真正性(Authenticity)とは、情報にアクセスする企業や個人が「アクセス許可された者」であることを確実にするものです。</span></p>
<h5>「真正性」を確保しないと…</h5>
<p><span> アカウントの乗っ取り、情報の漏えい、虚偽の申告など、情報を悪用されてしまう。</span></p>
<h5>どのような対策を取るか</h5>
<p><span> ・二段階認証を行う<br/> ・多要素認証を設定する<br/> ・デジタル署名を設定する<br/> ・アクセスを制限する </span></p>
<h3><span>「信頼性<span>」とは何か</span></span></h3>
<p><span> 信頼性(Reliability)とは、データやシステムを利用した動作が意図した通りの結果を出すことです。<br/> ヒューマンエラーやプログラムの不具合によって、期待する結果が得られない事もあります。このような事態を防ぐための対策が必要です。 </span></p>
<h5><span>「信頼性</span><span>」を確保しないと…</span></h5>
<p><span> システムの不具合が多発する。不要なファイルアクセスなどしデータ通信が重たくなり使用者が離れていく。サイトの評価が低くなる。</span></p>
<h5>どのような対策を取るか</h5>
<p><span> ・システムが不具合を起こさない設計や構築を行う<br/> ・ヒューマンエラーが起こってもデータ改ざんしない仕組みを施す<br/> ・不具合の早期発見できるような環境を整える<br/> ・リカバリーできるように仕組みを構築する</span></p>
<h3><span>「責任追跡性<span>」とは何か</span></span></h3>
<p><span> 責任追跡性(Accountability)とは、アクセスする企業や個人の動きを追跡して、データやシステムへの脅威が何であるか、どのような行為が原因なのかを追跡することです。</span></p>
<h5>「責任追跡性<span>」を確保しないと…</span></h5>
<p><span> 不具合原因の追究に膨大な時間がかかる。不正アクセスに気づきにくく、対策が取れない。ユーザー問い合わせに返信時間がかかる。</span></p>
<h5>どのような対策を取るか</h5>
<p><span> ・IPアドレスなどのアクセス履歴や通信ログを保存する<br/> ・操作履歴を保存する<br/> ・ログイン履歴を保存する<br/> ・デジタル署名を保存する </span></p>
<h3><span>「否認防止<span>」とは何か</span></span></h3>
<p><span> 否認防止(non-repudiation)とは、情報が後に否定されないように証明しておく事です。 </span></p>
<h5><span>「否認防止</span><span>」を確保しないと…</span></h5>
<p><span> 証拠不十分で情報が改ざん・利用した本人を糾弾できない。</span></p>
<h5>どのような対策を取るか</h5>
<p><span> ・IPアドレスなどのアクセス履歴や通信ログを保存する<br/> ・操作履歴を保存する<br/> ・ログイン履歴を保存する<br/> ・デジタル署名を保存する<br/> ・タイムスタンプを付与する<br/> ・フォレンジックを行える環境を整えておく</span></p>
<h3>まとめ</h3>
<p><span> セキュリティシステムが進化するのと同時にサイバー攻撃手法も日々進化、巧妙化しています。 そのため、あらゆるセキュリティ対策を検討し最新システムを導入することは大切なことです。<br/> さらに重要なのはそれらを「維持」することです。常に最新の情報へアップデートし、セキュリティ対策を更新していくことで、攻撃のリスクを低減できます。<br/> 企業でリモートが増えたことや個人情報保護法が改正されたこともあり、社員一人ひとりが情報の取り扱い対しての意識を高めることもとても重要になりました。また、個人・家庭内でもスマートフォンという便利なコンピュータを個人がもつ時代になり、個人へのサイバー攻撃も増大しています。<br/> <br/> 情報セキュリティとは、システムだけではなく自分の身の安全も守るものでもあるので、常に関心を持ち、知識をアップデートしていくことが重要なことでしょう。</span></p>