ラズパイ4でKubernetesクラスタを再構築したときに様々詰まった話

いつもGKEでやっていたのだけど、おうちで好き勝手に動かせるクラスタが欲しいと思い、構築

元々勤務している学校のサーバー室に、こっそりk8sクラスタとして仕掛けていたのだが、回収して自宅で再度構築し直した

その時に、様々つまづいたので、備忘録として

OSのインストール

OSはUbuntu 22.04 LTSを使用

Raspbery Pi Imagerを使用したところ、このようなメッセージが

「検証に失敗しているだけで、動かせはするやろ」と思ったのだが、動かず

ラズパイのACTランプが4回点滅を繰り返しているので、調べてみたところ

緑色のACTライトが4回点滅する場合は、コード「start.elf」が見つからなかったことを示しています。Start.elfは、ブートフォルダの先頭にある関数です。この機能が見つからない場合、RaspberryPiは起動しません。

Raspberry pi インジケーターランプ(ACTライト)の意味 - Qiitaより

どうやら本当に書き込めていないらしい。

別のmicro SDカードを刺してみたところ、問題なく動いたので、純粋にmicro SDが壊れていたみたい。

試しに使ったmicro SDがシリコンパワー製のもので、読み書き速度に難があったことから、あきばお〜でサンディスクの64GB micro SDカードを新規調達した。

gpgキー?

Dockerのインストールで、

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

をやったところ、

Warning: apt-key is deprecated. Manage keyring files in trusted.gpg.d instead (see apt-key(8)).

と出てきた。

どうやら、最近のaptでは、apt-key が非推奨らしい。

www.clear-code.com

ただ、一応動いているといえば動いているので、一旦無視して、気が向いたら対応する。

kubeletが動いてくれない

クラスタ構築後、Worker nodeを kubeadm join で追加したはいいのだが、nodeがいつまでも Ready にならず、 NotReady のままになってしまう。

clusteradmin@master:~$ kubectl get nodes
The connection to the server 10.0.100.1:6443 was refused - did you specify the right host or port?

それどころか、kubectl が時たま Connection Refused で使えなくなる始末。

なんでやと思ったのだけど、似たような感じの事例で困っている人の記事を発見。

tech.virtualtech.jp

最近のUbuntuでは、cgroupsがv1からv2になった関係で、containerd周りでコケているらしい?

とりあえず、記事に書いてある通り、cgroupsをv1に戻す対応をした。

Raspberry Pi + Ubuntuな環境でcrgoupsのバージョンを指定する際は、/boot/firmware/cmdline.txtsystemd.unified_cgroup_hierarchy=0を追記して、再起動

参考: raspberry pi os - Enable cgroups v1 in RaspberryPi OS - Raspberry Pi Stack Exchange

公式Docsを見てみると、/etc/containerd/config.tomlSystemdCgroupという項目をTrueにするとのこと。

確かに、現状の設定ファイルはこのようになっている

clusteradmin@master:~$ cat /etc/containerd/config.toml | grep Systemd
            SystemdCgroup = false

これ直すと、普通に動いたのかしら

追記:動いた

flannelのPodが立ち上がらない

コンテナ間通信のプラグインとして、flannelを使っているのだが、flannelのpodがいつまでもcrashしてしまう

clusteradmin@master:~$ kubectl get pods --all-namespaces
NAMESPACE      NAME                             READY   STATUS              RESTARTS         AGE
kube-flannel   kube-flannel-ds-bzqn6            0/1     Error               2 (20s ago)      27s
kube-system    coredns-787d4945fb-97f5n         0/1     ContainerCreating   0                10m
kube-system    coredns-787d4945fb-qbdc7         0/1     ContainerCreating   0                10m
kube-system    etcd-master                      1/1     Running             27 (2m53s ago)   11m
kube-system    kube-apiserver-master            1/1     Running             8 (2m15s ago)    10m
kube-system    kube-controller-manager-master   1/1     Running             10 (3m11s ago)   11m
kube-system    kube-proxy-d6jld                 1/1     Running             5 (3m37s ago)    10m
kube-system    kube-scheduler-master            1/1     Running             25 (3m4s ago)    9m38s

logにも、それっぽいエラーが出力されていた

clusteradmin@master:~$ kubectl logs kube-flannel-ds-bzqn6 --namespace=kube-flannel
Defaulted container "kube-flannel" out of: kube-flannel, install-cni-plugin (init), install-cni (init)
I0102 14:14:59.696494       1 main.go:204] CLI flags config: {etcdEndpoints:http://127.0.0.1:4001,http://127.0.0.1:2379 etcdPrefix:/coreos.com/network etcdKeyfile: etcdCertfile: etcdCAFile: etcdUsername: etcdPassword: version:false kubeSubnetMgr:true kubeApiUrl: kubeAnnotationPrefix:flannel.alpha.coreos.com kubeConfigFile: iface:[] ifaceRegex:[] ipMasq:true ifaceCanReach: subnetFile:/run/flannel/subnet.env publicIP: publicIPv6: subnetLeaseRenewMargin:60 healthzIP:0.0.0.0 healthzPort:0 iptablesResyncSeconds:5 iptablesForwardRules:true netConfPath:/etc/kube-flannel/net-conf.json setNodeNetworkUnavailable:true}
W0102 14:14:59.696794       1 client_config.go:617] Neither --kubeconfig nor --master was specified.  Using the inClusterConfig.  This might not work.
I0102 14:14:59.733776       1 kube.go:126] Waiting 10m0s for node controller to sync
I0102 14:14:59.733890       1 kube.go:431] Starting kube subnet manager
I0102 14:15:00.734163       1 kube.go:133] Node controller sync successful
I0102 14:15:00.734886       1 main.go:224] Created subnet manager: Kubernetes Subnet Manager - master
I0102 14:15:00.735003       1 main.go:227] Installing signal handlers
I0102 14:15:00.736299       1 main.go:467] Found network config - Backend type: vxlan
I0102 14:15:00.736395       1 match.go:206] Determining IP address of default interface
I0102 14:15:00.739258       1 match.go:259] Using interface with name eth0 and address 10.0.100.1
I0102 14:15:00.739424       1 match.go:281] Defaulting external address to interface address (10.0.100.1)
I0102 14:15:00.739649       1 vxlan.go:138] VXLAN config: VNI=1 Port=0 GBP=false Learning=false DirectRouting=false
E0102 14:15:00.748235       1 main.go:327] Error registering network: operation not supported
W0102 14:15:00.748933       1 reflector.go:347] github.com/flannel-io/flannel/subnet/kube/kube.go:432: watch of *v1.Node ended with: an error on the server ("unable to decode an event from the watch stream: context canceled") has prevented the request from succeeding
I0102 14:15:00.749578       1 main.go:447] Stopping shutdownHandler...

vxlanなるものが悪さをしているらしい。

vxlanとは?も含めて、この記事が参考になった。

qiita.com

podは複数のnode上にあるため、それらを同一のネットワーク内で接続するのに、vxlanという機能を使用しているらしいが、Ubuntu21.10あたりから含まれなくなったらしいので、ネットワークを作成できないみたい

ラズパイに関しては、linux-modules-extra-raspiというパッケージをインストールすることで、解決した。

それでも立ち上がらないflannel

またしてもflannelのpodが立ち上がらないので、再びログを洗った

clusteradmin@master:~$ kubectl logs kube-flannel-ds-5cq5f --namespace=kube-flannel
Defaulted container "kube-flannel" out of: kube-flannel, install-cni-plugin (init), install-cni (init)
I0102 14:21:39.663540       1 main.go:204] CLI flags config: {etcdEndpoints:http://127.0.0.1:4001,http://127.0.0.1:2379 etcdPrefix:/coreos.com/network etcdKeyfile: etcdCertfile: etcdCAFile: etcdUsername: etcdPassword: version:false kubeSubnetMgr:true kubeApiUrl: kubeAnnotationPrefix:flannel.alpha.coreos.com kubeConfigFile: iface:[] ifaceRegex:[] ipMasq:true ifaceCanReach: subnetFile:/run/flannel/subnet.env publicIP: publicIPv6: subnetLeaseRenewMargin:60 healthzIP:0.0.0.0 healthzPort:0 iptablesResyncSeconds:5 iptablesForwardRules:true netConfPath:/etc/kube-flannel/net-conf.json setNodeNetworkUnavailable:true}
W0102 14:21:39.663887       1 client_config.go:617] Neither --kubeconfig nor --master was specified.  Using the inClusterConfig.  This might not work.
I0102 14:21:39.700428       1 kube.go:126] Waiting 10m0s for node controller to sync
I0102 14:21:39.700527       1 kube.go:431] Starting kube subnet manager
I0102 14:21:39.707598       1 kube.go:452] Creating the node lease for IPv4. This is the n.Spec.PodCIDRs: [172.16.0.0/24]
I0102 14:21:40.701102       1 kube.go:133] Node controller sync successful
I0102 14:21:40.701695       1 main.go:224] Created subnet manager: Kubernetes Subnet Manager - master
I0102 14:21:40.701743       1 main.go:227] Installing signal handlers
I0102 14:21:40.702648       1 main.go:467] Found network config - Backend type: vxlan
I0102 14:21:40.702964       1 match.go:206] Determining IP address of default interface
I0102 14:21:40.705451       1 match.go:259] Using interface with name eth0 and address 10.0.100.1
I0102 14:21:40.705623       1 match.go:281] Defaulting external address to interface address (10.0.100.1)
I0102 14:21:40.705858       1 vxlan.go:138] VXLAN config: VNI=1 Port=0 GBP=false Learning=false DirectRouting=false
E0102 14:21:40.707784       1 main.go:327] Error registering network: failed to acquire lease: subnet "10.244.0.0/16" specified in the flannel net config doesn't contain "172.16.0.0/24" PodCIDR of the "master" node
I0102 14:21:40.707957       1 main.go:447] Stopping shutdownHandler...

こちらは、あまり情報が見つからなかったのだが、中国語(!?)で記事が見つかった。

blog.csdn.net

flannelのpodを作成するときに使用するyamlファイルの中に、このような設定項目がある

kind: ConfigMap
apiVersion: v1
metadata:
  name: kube-flannel-cfg
  namespace: kube-flannel
  labels:
    tier: node
    app: flannel
data:
  cni-conf.json: |
    {
      "name": "cbr0",
      "cniVersion": "0.3.1",
      "plugins": [
        {
          "type": "flannel",
          "delegate": {
            "hairpinMode": true,
            "isDefaultGateway": true
          }
        },
        {
          "type": "portmap",
          "capabilities": {
            "portMappings": true
          }
        }
      ]
    }
  net-conf.json: |
    {
      "Network": "10.244.0.0/16",
      "Backend": {
        "Type": "vxlan"
      }
    }

この中の"Network": "10.244.0.0/16",という部分で、podのCIDRを指定しているのだが、Kubernetesクラスタを作成した際のオプションで--pod-network-cidr=172.16.0.0/16と指定しており、ここが一致していないのが原因だった。 そのため、合わせた上でapplyし直したところ、うまく動き出した。

clusteradmin@master:~$ kubectl get pods --all-namespaces
NAMESPACE      NAME                             READY   STATUS    RESTARTS   AGE
kube-flannel   kube-flannel-ds-ssg4b            1/1     Running   0          18s
kube-system    coredns-787d4945fb-2bqlx         1/1     Running   0          3m5s
kube-system    coredns-787d4945fb-wx6w8         1/1     Running   0          3m5s
kube-system    etcd-master                      1/1     Running   29         3m21s
kube-system    kube-apiserver-master            1/1     Running   0          3m15s
kube-system    kube-controller-manager-master   1/1     Running   0          3m17s
kube-system    kube-proxy-mvk7d                 1/1     Running   0          3m5s
kube-system    kube-scheduler-master            1/1     Running   27         3m18s

Workerの方はどうなの?

今回、Workerの方もラズパイ4 + Ubuntu22.04という環境のため、cgroupsのバージョンを落とす + linux-modules-extra-raspi のインストールを行った上でkubeadm joinを実行する

感想

バージョンが新しくなったりして、いつまでも同じ情報が使えるわけではないなという所感

Kubernetesのエコシステムは巨大で、エラーの目星をつけるのもとても大変だった・・・

大学祭のシステムをフルGCPで運用してみた

10/31〜11/01に、東洋大学 赤羽台キャンパスの大学祭「INIAD-FES」はオンラインで開催となりました。 その際のシステムを、GCPでホストしたので、その時のシステムの構成や、構成してみての感想をまとめてみたというお話です。

なおここでは開発したシステムなどの詳細には触れません

↓にも同じ内容を掲載しています qiita.com

どんな大学祭?

今年はCOVID-19の影響が続く中、多くの大学祭がオンライン上での開催を余儀なくされました。 INIAD-FESも例外ではありませんでしたが、多くの大学祭でライブ配信+Webページ上での掲示にとどまったものになっているのに対して、Webページ上で(できるだけ)キャンパスを再現→ライブ配信や静的コンテンツを掲載するという形としました。 できるだけ、キャンパス内の雰囲気を感じてもらおうというコンセプトです。

使ったサービスたち

Google Kubernetes Engine

最も要となるものです。

  • 会場サイト フロントエンド(React + nginx)・バックエンド(Rails
  • 入構管理システム(Django
  • 動画配信サーバー(nginx)

などをホストしました。 これらは、ユーザーが触れるシステムであり、VMを直接は触りたくない(運用が大変なので)・負荷分散を行いたいという理由で、GKEを使用しました。

GKEの負荷分散は、Google ManagedなSSL証明書を無料で使用できるので、とても便利ですが、もうちょっと発行早くしてくれないかなぁって所感です。 なお、static-ipは先に予約しておいて、予めDNSレコードに登録しておいたほうが後が幸せ。

自分はDockerを雰囲気で使ってる人間なので、当初は「え、ただの仮想マシン的なやつでしょ?」と思って、入構管理システムでcronを叩くようにしたら爆死しました。 DjangoのContainerではDjangoしか動かないんですね・・・

なお、こんな感じのyamlを書いてあげれば、CronJobとしてGKEに登録が可能です。 確かに、これはこれでお行儀がいいなと感じました。

apiVersion: batch/v1beta1
kind: CronJob
metadata:
    name: sendmail
    namespace: health-reception
spec:
    schedule: "0 4,8,14 * * *"
    jobTemplate:
        spec:
            template:
                spec:
                    containers:
                      - name: sendmail
                        image: dummy
                        args:
                          - python3
                          - manage.py
                          - send_alert_mail
                        env:
                    restartPolicy: OnFailure

GCPロードバランサーは、WebSocketに対応しているんですが、なんかやたらめったら切断されるなぁって思ったら、設定されたタイムアウト時間で一旦切断されるんですね。 configを書いて、serviceの設定に適用するようにすれば、ひとまず問題を回避できました。

↓のbeta.cloud.google.com/backend-configで設定を適用しています。 ingressの方へはreadinessProbeと同じく、serviceの設定が伝わるようで、特に設定しなくても大丈夫でした。

apiVersion: cloud.google.com/v1beta1
kind: BackendConfig
metadata:
  name: venue-backend-config
  namespace: venue-backend
spec:
  timeoutSec: 3600
---
apiVersion: v1
kind: Service
metadata:
  annotations:
    beta.cloud.google.com/backend-config: '{"ports": {"3000":"venue-backend-config","443":"venue-backend-config"}}'
  labels:
    app: venue-backend
  name: venue-deployment-service
  namespace: venue-backend
spec:
  externalTrafficPolicy: Cluster
  ports:
  - name: 80-to-80-tcp
    nodePort: 31377
    port: 80
    protocol: TCP
    targetPort: 80
  - name: 3001-to-3000
    nodePort: 31401
    port: 3001
    protocol: TCP
    targetPort: 3000
  - name: 3000-to-3000-tcp
    nodePort: 30323
    port: 3000
    protocol: TCP
    targetPort: 3000
  - name: 3002-to-3000-tcp
    nodePort: 30472
    port: 3002
    protocol: TCP
    targetPort: 3000
  selector:
    app: venue-backend
  sessionAffinity: None
  type: NodePort

ただ、WebSocketはそれだけでポートを1つ占有し続けることになるので、あまりお行儀は良くないのかもしれませんね。

Google Cloud Storage

静的コンテンツのホストをしました。 キャッシュが行われて、変更が即座に反映されないというのが、ありがたいのだろうけどちょっと泣かされました。 単純なWebサイトであればホストできるんじゃね?って思ったんですが甘かった・・・

Cloud SQL

Postgresで、会場サイトや入構管理システムのデータを保持しました。 最初はDeployment内にPostgresのコンテナをもたせていたのですが、GKEのPersistent VolumeがReadWriteManyに対応しておらず、スケールできないことが開発途中で判明したので、急遽の変更です。

できるだけPaaSを使いましょうねってことですね。

Cloud Memorystore

Railsのチャット関係で、ActionCableを採用したことから、Redisのために使用しました。 結構高いので、一瞬躊躇したのですが、Cloud SQLと同じく、Deployment内に持たせているとPersistent Volumeの問題でスケールできなくなるため、採用 純粋なRedisだと思ってたら、Rails6の仕様との兼ね合いでトラブりました(参考

Cloud Build

これは初めて使いました。 自分はCI/CDは使ってこなかったのですが、開発メンバー からの圧力により が多め(当社比)で、デプロイの自動化は必要となったための採用です。 GitlabのCI/CDは無料でできる範囲に制限があること(途中で使い切りました)、GKEやGCRなどGCPのサービスへの権限をかんたんに付与できるということで、とても重宝しました。 なお、GCPサービスへの権限付与は、サービスアカウントを有効化するだけです、簡単ですね。

運用してみての感想

GCPかなり活用したつもりですが、実際にはVPCを意識していないなど、結構問題アリアリな感じです。 個人でGCP運用しているのにも活かせる知見は得られたんじゃないかなぁと思います。

あと、GKEで運用していた部分をGAEに置き換えた場合というのも、ちょっと興味あります。 そもそもGKEで運用するべきだったのかという辺りも、また探ってみたいと思いました。

at INIAD生の皆様 INIAD-FES実行委員会、エンジニアが深刻なレベルで不足中です。 実行委員会で↑みたいな大規模システムの運用、してみませんか?

WindowsでもRailsの開発環境をそろえてみた

Railsの開発は、基本MacBook Proで行っているので、何の感慨もなく環境構築できるのですが、WindowsだといろいろUNIX系OSとの差異が目立って、開発環境を準備できておらず、家のWindows DesktopがTwitter専用マシンと化していたので、この際開発環境を構築してみたのでメモです。

当初はWSL(無印)で頑張ろうとしていたんですが、Dockerがうまく動かなかったので、Insider Previewを有効化した上でWSL2を使用しています。

WSL2はWindows 10のbuild 2004から利用可能になります、リリースまで秒読み段階みたいなのですが、COVID-19の影響を受けているようで、リリースされそうだけどされないような状態になっています。。。 もう4月終わるよ

なお、IDEはRubyMineを使用します。

WSL2を有効化

qiita.com

にいい感じの方法が載っていたのでそのまま(おい

Ubuntuをインストール

数日前に、Ubuntu 20.04 LTSがリリースされましたが、人柱にはなりたくないので、おとなしく18.04 LTSを入れることにします。

Windows側にDocker Desktopをインストール

Ubuntu on WSLでは、Docker darmonは動かさず、Windows OS上で動いているDocker daemonを使用する形式のようです。

そこで、Windows側にDocker Desktopをインストールして、起動しておきます。

f:id:beah:20200428011435p:plain

WSL2で動くLinuxディストリビューションが、Settings→Resources→WSL INTEGRATIONに表示されているので、トグルをオンにしておきます。

Ubuntu上でDockerを動かしてみる

早速、動かしてみます。

f:id:beah:20200428011835p:plain

うん、いい感じですね!

rbenvをインストール

・・・中身Ubuntuなので、特に思うこともなく、インストールします

RubyMineにWSL上のRubyを読み込ませる

VSCodeでいうRemote的なものを使って、RubyMineでもUbuntuに入ったRubySDKを読み込ませます。

f:id:beah:20200428012153p:plain

Settings→Languages & Frameworks→Ruby SDK and Gemsを開き、「+」→「New Remote」

f:id:beah:20200428012446p:plain

Linuxディストリビューションを選択しますが、ここで注意したいのがRubyのパスです。

rbenvでインストールしたRubyは、どこにあると思いますか?見てみましょう。 (kentaroはユーザー名なので、適宜置換してください)

kentaro@DESKTOP1:~$ which ruby
/home/kentaro/.rbenv/shims/ruby

~/.rbenv/shims/rubyが、Windows側から見えない(というより、shimsというディレクトリは実際には存在しないようです)、RubyMineではエラーになってしまいます。

そこで、それぞれのバージョンごとに、パスを指定してあげます。

/home/kentaro/.rbenv/versions/{Rubyのバージョン}/bin/ruby

ターミナルもWSLを使うようにする

Dockerコマンドを叩いたりするのは、Ubuntu上で行います。

なので、RubyMineから見えるTerminalもUbuntuのものにしたいですよね。

f:id:beah:20200428014221p:plain

「Tools」→「Terminal」の「Shell path」をC:\Windows\System32\bash.exeにします。

f:id:beah:20200428014338p:plain

うん!いい感じですね!

まとめ

説明になってるようでなってないのですが、備忘録ということなので・・・

これで、Windows環境でも開発を進めるのが楽になります。

早くbuild 2004が正式リリースされて欲しい・・・

おまけ:Windows⇔WSLのファイルの扱い

最近のWSLでは、(WSL1も共通で)/mnt以下に、Windowsマシンのディレクトリがマウントされるようになっているようです。

なので、開発のプロジェクトなどはWindows OS側に置いて、Ubuntu側にはそこへのリンボリックリンクを置いておくという運用にしています。 (MacとプロジェクトをDrive経由で同期したいという需要もあるため)

Mac Catalyst 書初め

こんにちは 最近になってようやくSwiftUIを触り始めたので、この機会にということで、MacCatalystにも挑戦してみました

すごくざっくり言えば、iOSMacのアプリが同じコードで動くということです 実際触ってみた感想で言えば、夢を見過ぎるべきでないということですかね

というわけで、導入してみましょう

何を作る?

所用でTwitterAPIの申請を作成することになったんですが、その申請って英語なんですよね

英語力が死んだ自分には、書くのはしんどいので(そもそもあれ大して読まれてないって説もありますがどうなんですかね)翻訳ソフトに頼りますが、Google翻訳は信用してないので、NICTAPIを使うことにします そして、あのWebサイト、時間制限があるんですよね(24分間)

かといって、メモとかに残すのもだるいので、ネイティブアプリでAPIを叩く感じにします

プロジェクトの作成

プロジェクトの作成方法としては、iOSと一緒です f:id:beah:20200224202637p:plain

User InterfaceSwiftUIにするのを忘れないであげてください

f:id:beah:20200224202826p:plain

Deploy InfoTargetMacにチェックを入れます

f:id:beah:20200224202933p:plain

メッセージが出るのでEnableをクリックします

プロジェクトのMac対応は以上です

f:id:beah:20200224203048p:plain

Deviceの一覧に、MaciPhone/iPadが共存しているの、なんか新鮮ですよね

両方で動くんだという実感が湧きます

SwiftUI

import SwiftUI

struct ContentView: View {
    @State private var rawText = ""
    @State private var translatedText = ""
    var translationController = TranslationController(apiKey: "YOUR API KEY",
                                                      apiSecret: "YOUR API SECRET",
                                                      apiUserName: "YOUR USER NAME")
    
    var body: some View {
        VStack{
            TextField("翻訳元", text: $rawText)
                .padding(.horizontal)
            Button(action: {
                let (parameter, url) = self.translationController.generateParameter(fromLang: "ja", toLang: "en", text: self.rawText)
                self.translationController
                    .oauthClient!
                    .client
                    .post(url,
                          parameters: parameter,
                          completionHandler: {result in
                            switch result{
                            case .success(let response):
                                self.translatedText = self.translationController.getTranslatedText(result: response)
                            case .failure(let error):
                                print(error.description)
                                break
                            }
                    })
            }) {
                Text("翻訳")
            }.padding()
            
            Text(self.translatedText)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
import Foundation
import SwiftyJSON
import OAuthSwift

class TranslationController{
    var apiKey = ""
    var apiSecret = ""
    var apiUserName = ""
    var apiBaseUrl = "https://mt-auto-minhon-mlt.ucri.jgn-x.jp"
    
    var oauthClient:OAuth1Swift!
    
    init(apiKey:String?, apiSecret:String?, apiUserName:String?){
        if let apiKey = apiKey{
            self.apiKey = apiKey
        }
        
        if let apiSecret = apiSecret{
            self.apiSecret = apiSecret
        }
        
        if let apiUserName = apiUserName{
            self.apiUserName = apiUserName
        }
        
        self.oauthClient = OAuth1Swift(consumerKey: self.apiKey, consumerSecret: self.apiSecret)
    }
    
    func generateParameter(fromLang:String, toLang:String, text:String) -> (OAuthSwift.Parameters, String){
        var parameter = OAuthSwift.Parameters()
        parameter["key"] = self.apiKey
        parameter["name"] = self.apiUserName
        parameter["type"] = "json"
        parameter["text"] = text
        
        return (parameter, "\(self.apiBaseUrl)/api/mt/generalNT_\(fromLang)_\(toLang)/")
    }
    
    func getTranslatedText(result:OAuthSwiftResponse) -> String{
        guard let resultString = result.string else{
            return ""
        }
        
        let data = JSON.init(parseJSON: resultString)
        if let resultText = data["resultset"]["result"]["text"].string{
            return resultText
        }
        
        return ""
    }
}

コード読んでくださいって感じなんですが、ただ単に、翻訳元のテキストを投げて翻訳結果を取得するってだけですね

APIキー関係は、こちらからご自身で取得してみてください

ハマったところ

NICTAPIはOAuth1認証です(OAuth2ではありません)

自分でシグネチャを生成するのは面倒だと判断したので、OAuthSwiftを使いましたが、そいつがエラーに

f:id:beah:20200224204033p:plain f:id:beah:20200224204049p:plain

iOSだと動くのに、なんでやねんという感じなんですが、(OAuthSwift自体はMac対応を謳っています)sudo gem update cocoapodsでCocoapodsをアップデートして、(念の為)pod installしたら上手く動きました

バージョンの問題なんですかね

というわけで完成

f:id:beah:20200224204525p:plain

うん!いい感じですね!(UIが絶望的に下手っぴですが)

まとめ

SwiftUIは、これまでよりは表現が制約されますが、よしなにやってくれるというのがとても強いと思いました

AutoLayout未だに意味分かんねぇもん・・・

後は、ライブラリ関係がMac Catalystに対応してくれるといいんですが、なかなか時間かかるでしょうね・・・

そういう意味では、MaciOSが同じコードで動く!なんて、あまり夢を見過ぎるべきでないと感じました(どちらかというと、iPadOSとmacOSのペアなんですが、正直iPadOSとiOSの違いが自分にはよくわからない・・・)

ではでは〜

※追記:Storyboardでも、Mac Catalystは使えるみたいです、でも正直使いたくねぇ

ブログを作ってみた

ブログ、作ってみました

 

最初はKUSANAGI環境でWordPressかな・・・と思ったんですが、WordPressMarkdownとかシンタックスハイライトとか使いたいと思うと、なかなかしんどいものがあったので・・・

 

独自ドメインを使いたいので、いずれPro版にしますが、しばらくの間は無料版で

 

技術的な「これやってみた」をメインで上げていこうかなと思ってます