d.sunnyone.org
sunnyone.org

ページ

2019-11-11

DeskMini A300 + Ryzen 3400G でPCを組んだ

そろそろメインPCを新しくしなきゃなと思いながらもずっとできなかったメインPCの調達をついにやった。DeskMini A300という箱とRyzen 3400GというAPU。Ryzen APUがZen2なりになるまで待つべきかどうしようかというところで、目をつけていたDeskMini A300がツクモのセールで\12k程だったのでここだということで購入。キーボードと比較してこの大きさ。小さい。

構成は以下の通り。
ケースDeskMini A300 (+ USB2.0ポート追加ケーブル)
CPURyzen 3400G
ファンNoctua NH-L9a-AM4
メモリCrucial 32GB Kit (2 x 16GB) DDR4-2666 SODIMM (CT2K16G4SFD8266)
SSDWD BLACK SN750 NVMe 500GB (WDS500G3X0C)


このDeskMini A300、この小ささでM.2 SSD 2枚、2.5" SSD 2枚が入る。驚き。USBの口が少ないので、増設ケーブルは\1kちょいなので買っておくのがおすすめ。

CPUはRyzen 3200Gでいいかと思ったけど、結局3400Gにしてしまって、いらなかったかなと思いつつ、あとで困るよりはいいかなと。ファンはケース付属ではパワー不足ではということで、ちょっと高めだけど安牌らしいNH-L9a-AM4をチョイス。

メモリは16Gでいいかなと思いつつ、VRAMに2G持っていくそうなので、まぁ後で増やすのも面倒だしと思って32GBにしてしまった。

SSDはこの小ささなのでM.2のタイプを。Intelのやつにしようかなと思ったのだけど、WD BLACKは発熱が抑えられているそうで、これにしてみた。

ベンチマークを取ってから載せようと思ったけど、先送りされそうなのでとりあえず構成だけ。

前のPCと比較しての効果はなんといっても4K/60fps出せるようになったこと。画面がきれい。前のPCは4K出せなかったので、4Kディスプレイの意味がなかった。

2019-09-21

SPA用にwebpack-dev-serverにproxyを設定する

SPA (Single Page Application) でWebアプリケーションを作る場合、HTMLの生成やREST APIはJavaScript/CSSの配信とは別のサーバーで行うものの、クラウド環境ではCloudFrontなりリバースプロキシなりが存在していて、結果全て同一ドメインで運用されていることがある。

この状況で、JavaScriptをローカル環境のwebpack-dev-serverで試験しようとすると、REST APIが別ポートになるのでローカル環境でだけCORSの設定が必要だったり、深いパスでリロードするとNot Foundになったりといったような面倒ごとが発生する。

これを解決するには、nginxなりでproxyを立てても良いが、webpack-dev-serverにもproxyの機能がついているので、以下のように設定すると簡単。

    devServer: {
        contentBase: 'dist',
        host: '0.0.0.0',
        port: 3000,
        proxy: {
            '/myapp/api': {
                target: 'http://127.0.0.1:9292'
            },
            '/myapp': {
                target: 'http://localhost:3000/examples/index.html',
                pathRewrite: {'^.*' : ''}
            }
        }
    }

examplesにローカル向けのindex.htmlを置いておいて、CopyWebpackPluginでコピーすると便利。

        new CopyWebpackPlugin([
            { from: 'examples', to: 'examples' }
        ]),

2018-10-04

Spring Data JDBC で Enum を序数でない数値に変換する

Spring Data JDBC では、デフォルトではenumはnameに変換される。しかし、DBに格納する際は特定のコード(数値)に変換したいことも多いと思うので、その方法について書く。
Kotlinで書いてしまうが、Javaへの読み替えは難しくないと思う。

インタフェースの作成と利用

enumはordinal(序数)を持っているが、それぞれの項目で固定されたコード(数値)を設定したいので、コードを設定できるinterfaceを作成する。

interface CodedEnum {
    val code: Int
}
enum側で使っておく。
enum class Status(override val code: Int): CodedEnum {
    SUCCESS(1),
    FAILURE(2),
}

NamedParameterJdbcTemplateをカスタマイズしてパラメータを差し替える

Spring Data JDBC は内部でSpring JDBC のNamedParameterJdbcTemplateを使っているが、これが@Query で指定したパラメータの変換を担っている。 パラメータをenumで受け取るには、これがCodedEnumを扱えるようになる必要があるので、カスタマイズされたNamedParameterJdbcTemplateを準備する。
対応してる箇所が低いところすぎて気持ち悪いけど、getPreparedStatementCreator() は protected なので、NamedParameterJdbcTemplate的にはここを置き換えることは想定されているんだろうなと思いつつ対応。 enumに限らずここを書き換えたい作業はあると思うので、将来的にはSpring Data JDBC がもっと便利になって、こういう処理はいらなくなるかも。

Converterの追加とCustomizedNamedParameterJdbcTemplateの適用

@EnableJdbcRepositories の @Configuration で、まずnamedParameterJdbcTemplateを差し替える。

そして、値の変換を行うJdbcCustomConversionsにもEnumの変換処理を入れる。基本的には、write方面はinterfaceをそのまま.codeで変換して、readは型がわからないのでfactoryを利用して変換する。 ここでポイントになるのは、Spring Data JDBC の BasicRelationalPersistentPropertyがDB向け変換を行う際にEnumをStringに変換するということ。 これに対応するため、to Stringの変換を用意しておく。

 これで、codeで設定した値が利用されるようになる。

2018-03-26

AWS CloudFront Lambda@Edge の罠たち

CloudFront Lambda@Edge は、Lambda関数を実行しCloudFrontのリクエスト/レスポンスに手を加えることができるサービス。CloudFront⇔オリジン間、ビューワ⇔CloudFront間のリクエスト側/レスポンス側の4つの場所から適用する場所を選ぶことができる。

適切に使えば便利だと思うが、使ってみないとわからない罠が多いので、経験した範囲をまとめておく。

CloudFrontにはバージョンつきのLambda関数のARNを設定する必要がある

CloudFront BehaviorにLambda関数のARNを設定するのだが、名前だけのARNは設定することができず、バージョン番号入りのものを設定する必要がある。

つまり、Lambda関数の中身を書き換えたら、新しいバージョン番号を発行して、その番号をCloudFrontに設定する必要がある。

毎回手動でなんて現実的ではないので、AWS Serverless Application Model (AWS SAM)を利用してCloudFormationでLambda関数をデプロイし、AutoPublishAlias 機能によってバージョン番号を自動的に設定するのが現実的であろう。同一スタックであれば、CacheBehaviorsに以下のように設定する。
LambdaFunctionAssociations:
  - EventType: origin-response
    LambdaFunctionARN: !Ref "Function.Version"
これが何を意味するかというとLambda関数を更新する度にCloudFront Distributionを更新する、すなわちコードを書き換えるたびに数十分かかるということを意味する。

Lambda@Edgeに設定する関数はus-east-1リージョンに配置する必要がある

Lambdaはap-northeast-1でも利用可能だが、Lambda@Edgeに設定する関数はus-east-1に配置する必要がある。先のCloudFormationスタックを作るのであれば、us-east-1リージョンで作る必要があるということになる。

日本のサービスであればap-northeast-1に配置することも多いと思うが、スタック間でのパラメータの引継ぎに利用できるクロススタック参照は同一リージョンでしか利用できない。したがって、us-east-1→ap-northeast-1のスタック間でリソースの参照がある場合、自前でパラメータ引き渡しをするための仕組みが必要となる。

Lambda@Edgeで設定した関数は各リージョンにレプリカが作られて実行される

us-east-1にLambda関数を設定したからといって、us-east-1で常に実行されるかというとそうではなく、CloudFrontのエッジのリージョンに必要なタイミングでコピーされて実行される。

「オレがCloudFront エッジをサービスしたいのは日本だけ!」ということはできないので、日本を含もうとすると、多くのリージョンにレプリカが作られることになる。

レプリカはLambdaから見ると、us-east-1.というprefixがついた変更不可能な関数にしか見えない。これは、Lambda@Edgeに設定した関数のログは各リージョンのCloudWatch Logsに散らばって配置されるということを意味する。 もしログを集約したりフィルタしたりといったことをしようとすると、ほぼ全リージョンのCloudWatch Logsのリソースに対して手続きを行う必要がある。

Lambda@Edgeでは環境変数は利用できない

Lambdaで環境ごとの値を設定するのに便利な環境変数は、Lambda@Edgeでは利用できない。よって、コードに埋め込むか何らかのAPI問い合わせで値を取得する必要がある。今回 必要な値は別のリソースのARNだったので、レプリカ元(マスター)のLambda関数リソースのタグに設定し、以下の手順で取得した。
  1. context.invokedFunctionArnからレプリカ関数のARNを取得
  2. レプリカ関数のARNからリージョンを取得し、そのリージョンに対してレプリカ関数を問い合わせるlambda:GetFunctionの呼び出しを行いマスター関数のARNを取得
  3. マスター関数のARNからリージョン(us-east-1)を取得し、lambda:GetFunctionを呼び出してタグを取得
別の関数の環境変数だとか、DynamoDBだとか、状況により選択肢はあると思うが、いずれにせよ工夫が必要である。

Lambdaよりもタイムアウトなどの制限が厳しい

通常のLambda関数よりも、制限が厳しくなっている(Lambda@Edge の制限)。

例えば、Lambda単体であればタイムアウトは300秒まで設定できるが、オリジンリクエスト/レスポンスに対する関数だと30秒、ビューワーだと5秒など項目によっては厳しくなっているので注意が必要。アップロードサイズも注意。

以上のように使えそうに見えて使ってみるとどうしよう、みたいなところが多いので皆様お気をつけください。

2017-10-20

Kotlinでコマンドラインツールを作成する(Gradle/fat jar)

Kotlinを始めるにはコマンドラインツールを作ってみてはと言いつつ、そういう話あんまりなくない?と思ったので書くことにした。 Gradleでfat jarを生成し、javaがあれば実行できるようにするところまで。

sdkmanでGradleをインストール

http://sdkman.io/install.html からsdkmanをインストールして、gradleをインストールする。
$ sdk install gradle

gradle initで雛形の作成

適当なディレクトリを作成し、gradle initでbuild.gradle他ファイルを生成する。

$ mkdir kotlin-cmd-example
$ cd kotlin-cmd-example
$ gradle init

build.gradleの編集

Using Gradle - Kotlin Programming Languageを参考にして、build.gradleを記述する。

kotlin-gradle-pluginを適用して、kotlin-stdlib他必要なライブラリを追加すればOK。

ここではlog出力のためのlogbackのほか、fat jarを生成するための johnrengelman/shadow を使っている。 shadowの設定については Shadow Plugin User Guide & Examples を。

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.1.51'
    id 'com.github.johnrengelman.shadow' version '2.0.1'
}

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.jetbrains.kotlin:kotlin-stdlib-jre8'

    compile 'org.slf4j:slf4j-api:1.7.25'
    compile 'org.slf4j:jcl-over-slf4j:1.7.25'
    compile 'org.slf4j:jul-to-slf4j:1.7.25'
    compile 'ch.qos.logback:logback-classic:1.2.3'
}

apply plugin: 'application'
mainClassName = "com.example.kotlincmdexample.KotlinCmdExampleKt"

注意点としては、特に設定しないとJava側からはクラス名にはKtがついているのでつける。

src/main/kotlin に Kotlin ソースコードを作成

デフォルトではソースの場所はsrc/main/kotlinなので、ここにソースコードを配置していく。 IntelliJ IDEAであれば、build.gradleを開けば開始できる。

src/main/kotlin/com/example/kotlincmdexample/KotlinCmdExample.kt

package com.example.kotlincmdexample

import org.slf4j.LoggerFactory

fun main(args: Array<String>) {
    val log = LoggerFactory.getLogger("com.example.kotlincmdexample.KotlinCmdExample")

    if (args.isEmpty()) {
        println("Usage: kotlin-cmd-example name")
        return
    }

    log.info("Hello, {}", args[0])
}

あえてclassを作ってないのでgetLoggerが冗長な感じがするが、中途半端なのでloggerの名前を適当にしてもいいし、クラスを作って渡すかしたほうがいいかも。

./gradlew runでテスト実行

./gradlew runすれば実行できる。IDEAであれば右クリックしてRunあるいはDebugでもOK。

$ ./gradlew run 

> Task :run
Usage: kotlin-cmd-example name


BUILD SUCCESSFUL in 0s
2 actionable tasks: 1 executed, 1 up-to-date

./gradlew buildでビルド(jar生成)

./gradlew buildするとbuild/distributionsにkotlin-cmd-example-shadow.tarと.zipが入っているので、これを展開すると起動スクリプトがあるので実行できる。

build/libsに-all.jarがあるので、java -jarで直接起動することも可能。

$ java -jar build/libs/kotlin-cmd-example-all.jar 
Usage: kotlin-cmd-example name

$ java -jar build/libs/kotlin-cmd-example-all.jar MyFirstKotlinProject
19:49:00.796 [main] INFO com.example.kotlincmdexample.KotlinCmdExample - Hello, MyFirstKotlinProject

今回のプロジェクト

GitHubのsunnyone/kotlin-cmd-exampleに置いておくので、参考になれば。

2017-06-27

Kotlin で引数の名前つきでの呼び出しを強制する裏ワザ

小ネタ。

Kotlinでは引数を名前つきで呼び出せる。なしでもOK。
fun add(x: Int, y: Int) = x + y

fun f() {
    add(1, 2)
    add(x=1, y=2)
    add(1, y=2)
}

名前付きで呼んでほしいときはどうするか?名前つきにしてほしいところの前にvararg Voidをおく。

fun add(vararg useNamed: Void, x: Int, y: Int) = x + y

fun f() {
    // add(1, 2) // コンパイルエラー!
    add(x=1, y=2)
    // add(1, y=2) // コンパイルエラー!
}

途中からでもOK。
fun add(x: Int, vararg useNamed: Void, y: Int) = x + y

fun f() {
    // add(1, 2) // コンパイルエラー!
    add(x=1, y=2)
    add(1, y=2)
}

…いやいやだめだろこれ。もっといい方法があれば教えて下さい。
参考:How can I force calls to some constructors/functions to use named arguments?

2017-01-29

RTX810 からmydnsにIPアドレスを通知する(Luaをスケジュール実行する)

変わるIPを知ってるのはルータなので、ルータに通知してもらおうということでRTX810にmydnsの更新をさせることにする。なんのことはない、shiwork氏のLuaスクリプトをRTXで動かそうというだけの話。

ただし、いわゆるフラッシュ領域にtftpでスクリプトをもってきて実行するというネットワーク機器にありがちそうなやり方をしようとすると、いくつかのドキュメントにまたがって追わないといけないので、あとで追えるようにまとめておく。

スクリプトの準備

gistからスクリプトを持ってきて、ユーザID/パスワードを書き換えておく。

tftpによるLuaスクリプトの転送

RTXのいわゆるフラッシュ領域はRTFSというらしい。ここにディレクトリを作ってスクリプトを転送する。
(→RTFS

/scripts/ディレクトリの作成

ルートにカスタムなスクリプトをべたに置くと気持ち悪いので、/scriptsというディレクトリを作って置くことにする。
# make directory /scripts
# show file list /
2017/01/29 21:12:50 <DIR>           scripts

スクリプト転送元端末からのtftpアクセスを許可する

# tftp host 192.0.2.10

tftpで送り込む

c:\> tftp 192.0.2.254 PUT mydns.lua /scripts/mydns.lua/(administratorパスワード) 
adminパスを入れる必要があるのに気づかずにはまった。
(→RTシリーズのインストールとリビジョンアップに関するFAQ tftpの機能と注意事項

確認。
# show file list /scripts
2017/01/29 21:12:55            2769 mydns.lua

tftpの無効化

# tftp host none

Luaスクリプトの実行

単発実行

単発の実行はlua パスでOK。show status luaでsuccessなことを確認する。
# lua /scripts/mydns.lua
# show status lua
Lua Library Version:            Lua 5.1.5
Lua Script Function Version:    1.07

[running]
There is no running script.

[history]
(1)
Running Trigger:     executed by 'lua' command
Command Line:        lua /scripts/mydns.lua
Script File:         /scripts/mydns.lua
Running Counts:      1
Error Counts:        0
First Starting Date: 2017/01/29 21:14:52
Last Starting Date:  2017/01/29 21:14:52
Last Ending Date:    2017/01/29 21:14:53
Last Result:         success

スケジュール実行

scheduleコマンドで実行をスケジュールする。
schedule at 2 */* *:0,10,20,30,40,50 * lua /scripts/mydns.lua
これでおしまい。
(→Lua スクリプト機能