ortの灰ログ

人狼のことや技術のことや日々雑感

Oracle CloudのVM.Standard.A1.Flexでarm64用のSpring Boot docker imageをbuildする

この記事は

タイトルとっちらかってますね
Always Free枠で借りているVM.Standard.A1.Flexに構築したKubernetes上でSpring Bootプロジェクトを動作させたいので、
同環境に構築したself-hosted runnerでarm64用のdocker imageをbuildしてghcr.ioにpushするところまでやろう、という記事です。

前提

  • VM.Standard.A1.FlexGithub Actionsのself-hosted runnerが設定済み
  • Java8(他でもdocker imageやjdkが見つかればいけるはずです)

参考: Oracle CloudのVM.Standard.A1.FlexにGithub Actionsのself-hosted runnerを設定する - ortの灰ログ

やること

  • Spring Boot
    • jibを使ってarm64用のdocker imageを作成する設定を追加
  • Github Actions
    • developブランチにpushされたらVM.Standard.A1.Flex上でdocker image build
    • ついでにghcr.ioにpush

Spring Boot

jibを使ってdocker imageをbuildしてpushする設定を追加

GitHub - GoogleContainerTools/jib: 🏗 Build container images for your Java applications. を使います。

※ gradle かつKotlinでの例になるので、maven等使っている方は読み替えてください。

やることは単純で、build.gradle.ktsに若干設定を追加するだけです。

build.gradle.kts

plugins {
    java
    id("org.springframework.boot") version "2.3.0.RELEASE"
    id("io.spring.dependency-management") version "1.0.9.RELEASE"
    kotlin("jvm") version "1.3.72"
    kotlin("plugin.spring") version "1.3.72"
    # 追加
    id("com.google.cloud.tools.jib") version "2.6.0"
}

...

# 以下追加
jib {
    from {
        image = "arm64v8/openjdk:8"  # DockerHubで探してarm64用のopenjdkを使う。
        platforms {
            # linux & arm64を指定する
            platform {
                architecture = "arm64"
                os = "linux"
            }
        }
    }
    to {
        image = "ghcr.io/{githubユーザ名}/{docker image名}"
    }
    container {
        # この辺りはお好みで
        jvmFlags = listOf(
            "-server",
            "-Djava.awt.headless=true",
            "-Dspring.profiles.active=production"
        )
        creationTime = "USE_CURRENT_TIMESTAMP"
    }
}

Github Actions

  • developにpushされたら起動
  • 環境を汚さないようにJDKセットアップ(arm64版)
  • buildしてghcr.ioにpush

完成形

.github/workflows/deploy.yml

name: deploy

on:
  push:
    branches:
      - develop

jobs:
  delivery:
    runs-on: [ self-hosted ]
    steps:
      - name: clone repository
        uses: actions/checkout@v2
        with:
          ref: develop

      - name: download arm64 JDK 8
        run: |
          download_url="https://cdn.azul.com/zulu-embedded/bin/zulu8.54.0.21-ca-jdk8.0.292-linux_aarch64.tar.gz"
          wget -O $RUNNER_TEMP/java_package.tar.gz $download_url

      - name: set up JDK 8
        uses: actions/setup-java@v2
        with:
          distribution: 'jdkfile'
          jdkFile: ${{ runner.temp }}/java_package.tar.gz
          java-version: '8'
          architecture: arm64

      - name: build
        run: |
          ./gradlew jibDockerBuild

      - name: deploy
        run: |
          docker login ghcr.io -u {githubユーザー名} -p ${{ secrets.PACKAGE_PAT }}
          docker push ghcr.io/{githubユーザー名}/{jibに設定したdocker image名}

事前準備

write:packages権限がついたPersonal Access Tokenを用意

https://github.com/settings/tokenswrite:packages権限がついたトークンを作成、トークンをメモしておく

Secretsにトークンを設定

Githubの該当リポジトリでSettings -> Secrets -> New repository secretと進んで先ほどのトークンを設定。
ここではPACKAGE_PAT という名前にする。

JDKインストール

ここからはymlの話。
self-hosted runnerが動作するサーバーにJDKをインストールしても良いのだが、
環境を汚したくないので
GitHub - actions/setup-java: Set up your GitHub Actions workflow with a specific version of Java を使用する。

ただし、

      - uses: actions/setup-java@v1
        with:
          java-version: 8

とかにしてもx64用が使われてしまうのでarm64では動作しない。

https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Installing-Java-from-local-file

にあるように、自分で用意したarm64用のjdkを使わせる必要がある。
というわけで、こんな感じにすればできた。

      - name: download arm64 JDK 8
        run: |
          download_url="https://cdn.azul.com/zulu-embedded/bin/zulu8.54.0.21-ca-jdk8.0.292-linux_aarch64.tar.gz"
          wget -O $RUNNER_TEMP/java_package.tar.gz $download_url

      - name: set up JDK 8
        uses: actions/setup-java@v2
        with:
          distribution: 'jdkfile'
          jdkFile: ${{ runner.temp }}/java_package.tar.gz
          java-version: '8'
          architecture: arm64

他のバージョンを使いたい場合は同じようにarm64版のjdkを探してください。確か11は見つかった記憶があります。

jibでbuildしてpush

これは簡単。便利ですね。 ghcr.ioへpushするので先ほど設定したwrite:packages権限がついたpersonal access tokenを使っています。

      - name: build
        run: |
          ./gradlew jibDockerBuild

      - name: deploy
        run: |
          docker login ghcr.io -u {githubユーザー名} -p ${{ secrets.PACKAGE_PAT }}
          docker push ghcr.io/{githubユーザー名}/{jibに設定したdocker image名}

Oracle CloudのVM.Standard.A1.FlexにGithub Actionsのself-hosted runnerを設定する

この記事は

Always Free枠で借りているVM.Standard.A1.Flexを使ってself-hosted runnerを動作させる。
特に苦労しないだろうと思ったら少しハマったので備忘録としてメモしておきます。

やろうとしたこと

Githubの画面でAdd runnerボタンを押下して

  • Operating System: Linux
  • Architecture: ARM64

を選択

記載されているコマンドを順々に発行

ハマったこと

./config.sh --url ${repository url} --token ${token}

の箇所で

ldd: ./bin/libSystem.Security.Cryptography.Native.OpenSsl.so: No such file or directory

等のエラーが発生
ググった感じどうやらLinux arm64版で発生している問題らしい

こうした

issueのコメントを読んでみるとパスを書き換えてやればいけるそうなので、

sed -i -e 's/libSystem./System./g' config.sh

で無理矢理書き換えて実行したらうまくいきました。

(2021/1時点でissue報告されているのだけどまだ直ってないっぽい?)

自前で構築したkubernetesクラスタにmysql構築したメモ

この記事は

oritone.hatenablog.com

の続きで、
構築したkubernetesクラスタにとりあえずmysqlを構築した際のメモです。

やりたいこと

  • Oracle Cloud無料枠に構築したkubernetesクラスタmysqlを使えるようにする
  • workerで動作させる
  • podが作り直されてもデータが消えないよう永続化もする

永続化用の設定

こんな感じでPersistentVolumeとPersistentVolumeClaimの設定を書いて反映させる。
20GB確保、/data/mysql に永続化。

mysql-storage.yml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: db-pv
  labels:
    type: local
spec:
  storageClassName: manual
  capacity:
    storage: 20Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: /data/mysql
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: db-pvc
spec:
  storageClassName: manual
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
$ kubectl apply -f mysql-storage.yml

$ kubectl get pv,pvc

NAME                          CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                 STORAGECLASS   REASON   AGE
persistentvolume/db-pv   20Gi       RWO            Retain           Bound    default/db-pvc   manual                  10h

NAME                                STATUS   VOLUME       CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/db-pvc   Bound    db-pv   20Gi       RWO            manual         10h

秘匿情報を先に反映

後で必要になるのでrootのパスワードとかをSecretに入れて反映しておく

パスワードにしたい文字をbase64

$ echo -n {パスワードにしたい文字列} | base64

Secretに入れて反映

mysql-secret.yml

apiVersion: v1
kind: Secret
metadata:
  name: mysql-secret
type: Opaque
data:
  root_password: aG9nZQ== #ここにbase64化した文字列を入れる
$ kubetcl apply -f mysql-secret.yml

worker nodeで動作させたいのでlabelをつけておく

まずnodeの名前を確認して

$ kubectl get nodes
NAME           STATUS   ROLES                  AGE     VERSION
vm-1   Ready    control-plane,master   3d22h   v1.21.1
vm-2   Ready    <none>                 3d21h   v1.21.1

key=valueで名前をつける。

kubectl label nodes vm-2 nodeName=worker

mysql構築

mysql公式のDocker imageを使う。
mysql 使えば余裕じゃんと思っていたのだが、
今回使用しているVM.Standard.A1.FlexのCPUはarmベースとなっており、
mysql のDocker imageは現状armに対応していないため動作しなかった。
どうやら、mysql-server は対応しているらしいので、そちらを使う。

設定

こんな感じでDeployment, Service, ConfigMap等を書いて反映。
mysql-serverは8.0にした。
my.cnfの設定はお好み。

mysql.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
        - image: mysql/mysql-server:8.0
          name: mysql
          env:
            - name: MYSQL_ROOT_PASSWORD
              # 先ほど設定したSecretから取得
              valueFrom:
                secretKeyRef:
                  name: mysql-secret
                  key: root_password 
            - name: TZ
              value: Asia/Tokyo
          imagePullPolicy: Always
          ports:
            - containerPort: 3306
          volumeMounts:
            - name: mysql
              mountPath: /var/lib/mysql
            - name: my-cnf
              mountPath: /etc/my.cnf
              subPath: my.cnf
              readOnly: true
      # 先ほど設定したlabelをnodeSelectorで指定する
      nodeSelector:
        nodeName: worker
      volumes:
        - name: mysql
          # 先ほど設定したPVCを指定する
          persistentVolumeClaim:
            claimName: db-pvc
        - name: my-cnf
          configMap:
            name: db-configmap
            items:
              - key: my.cnf
                path: my.cnf
---
apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  selector:
    app: mysql
  type: NodePort
  ports:
  - protocol: TCP
    port: 3306
    targetPort: 3306
    # nodePort: 30000 # 手元のPC等から確認したい場合に設定(当然セキュリティは落ちる)
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: db-configmap
  labels:
    app: mysql
data:
  my.cnf: |-
    [mysqld]
    bind-address=0.0.0.0
    character-set-server=utf8mb4
    collation_server=utf8mb4_unicode_ci
    lower_case_table_names = 1
    transaction-isolation=READ-COMMITTED
    sql_mode = 'TRADITIONAL'
    skip-name-resolve=1
    default_authentication_plugin=mysql_native_password

    [client]
    default-character-set=utf8mb4
kubectl apply -f mysql.yml

containerの外から接続できるようにする

参考記事にも記載されているが、
mysql-serverの場合はこんな感じになっている(rootのhostがlocalhost)のでpodの中からしかDBに接続できない。

mysql> select user,host from mysql.user;
+------------------+-----------+
| user             | host      |
+------------------+-----------+
| healthchecker    | localhost |
| mysql.infoschema | localhost |
| mysql.session    | localhost |
| mysql.sys        | localhost |
| root             | localhost |
+------------------+-----------+

ので、一度podに入って外から接続できるユーザを作ってあげる必要がある。
(以下はrootを作る例)

$ kubectl get pods

NAME                       READY   STATUS    RESTARTS   AGE
mysql-658d66ff85-nz46j   1/1     Running   0          110m

$ kubectl exec --stdin --tty mysql-658d66ff85-nz46j -- /bin/bash

bash-4.4# mysql -uroot -p
create user root@'10.%';
grant all privileges on *.* to root@'10.%' with grant option;

確認

$ kubectl get services

NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
mysql      NodePort    10.X.Y.Z   <none>        3306:30000/TCP   116m

worker側で

$ mysql -uroot -h 10.X.Y.Z

で接続できるところまで確認できた。 root以外でも同じ要領でやれば良さそう。
vmインスタンス外からもアクセスしたい場合は
ServiceのnodePort設定、vmインスタンスiptables穴あけ、OracleCloud上で同じく穴あけが必要なので注意。

(自分はなんとなくrootはpodの中でだけアクセス可能なままで良いやと思ったので消した)

Oracle Cloudでつよつよスペックの無料枠を獲得してkubernetesクラスタを構築したメモ

この記事は

他の記事を参考にしながら構築していったのでメモを残しておきます。

参考にしました

Oracle Cloudの無料枠でKubernetesクラスタを構築する(完全版) | blog.potproject.net
無料で自分だけのオンラインストレージを建てる(Oracle Cloud)

つよつよスペックの無料枠って?

Always Free Resources

2021/6現在、

  • VM.Standard.A1.Flexを4 OCPU/24 GBまで自由に割り当て可能
    • 2CPU12GB + 1CPU6GB + 1CPU6GB とか、2CPU12GB + 2CPU12GBとかもできる
  • Public IPアドレス3つまで(エフェメラル2+予約済み1)可能
  • Block Volume合計200GBまで(ただしVM1つにつき50GB使われる)可能

という太っ腹仕様。
VPS換算にすると数千円レベル。

ちなみにVM.Standard.A1.FlexはarmベースなのでM1 Macのように一部制約が出てきます。
(これはまた別の記事で)

Oracle Cloud SignUp

東京リージョンは激戦区らしいので大阪リージョンで。
Optional項目も埋めないとsubmitできない箇所があった。(どこかは覚えていない)

VMインスタンスを作る

上記を2つ(master用、worker用)にした。
なかなか取得できないが、他の作業しながら1時間に1回程度チャレンジしたらなんとかその日のうちに取得できた。
kubernetesクラスタを組みたいのでネットワークは同じものを選択。
(一度間違えて別にして作り直した)

Block Volumeを作る

無料枠は200GBまでで、VMインスタンス1つにつき50GBのboot volumeが確保されるらしいので、残りは100GB。
masterにattachするつもりで100GBのものを1つ作成した。

作成したBlock volumeをVM instanceにattach

master用にattach。
無料で自分だけのオンラインストレージを建てる(Oracle Cloud) の手順6までを参考にする。
このあたりの設定のやり方はほぼ忘れているのでありがたい...

kubenetes構築

Oracle Cloudの無料枠でKubernetesクラスタを構築する(完全版) | blog.potproject.netを参考にして構築。
手順通り進めると

sudo apt-get install -y kubelet kubeadm kubectl

のあたりで
Installing kubeadm · Issue #25174 · kubernetes/website · GitHub
と同じ問題が発生してコケた。(正確には、その前のsudo apt-get updateでコケてる)
これは、

sudo apt remove apt-transport-https
sudo curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl

のように一旦apt-transport-httpsをアンインストールすれば進められた。

あとは、参考にした記事の後の方に書いてあるが、
workerノードでkubeadm joinする前にOracle Cloudで10.0.0.0/16の全ての通信を許可しておかないとjoinできない。(当たり前体操)

おわり

これでkubeadmを利用したkubernetesクラスタ(といってもワーカー1つだけど)構築完了。

続き

oritone.hatenablog.com

HOWLING WOLF、FIREWOLF、LASTWOLFの技術スタック

まえがき

さすがに書いてるだろと思ったら書いていなくて草生えました。
HOWLING WOLFFIREWOLFLASTWOLFの技術スタックです。
3サイトともほぼ同じです。

バックエンド

インフラ

あまりお金をかけても採算がとれないので相変わらずのさくらVPSです。
(お金をかけない場合の手段の話はまた今度書こうかなと思ってます)

技術スタック

項目 採用したもの
言語 Kotlin
フレームワーク SpringBoot
認証 Firebase Authentication(JWT)
O/R Mapper DBFlute
DB MySQL
CI/CD Github Actions
Webサーバー Apache
APサーバー Apache Tomcat

WOLF MANSIONを作っている最中に考えていたのですが、
ビジネスロジックがかなり複雑、その中でもリスト操作が多いので、
言語的に最初からやりやすそうということでサーバサイドKotlin+DDDにしてみました。
DDDは最初は難しかったのですがテストも書きやすいですしかなり良いですね。
KotlinはJavaをやっていたのでかなりとっつきやすかったです。一番好み。

フロントエンド

項目 採用したもの
認証 Firebase Authentication
言語 JavaScript(TypeScript)
フレームワーク Nuxt.js
状態管理 Vuexfire
CSSフレームワーク Buefy

ワードウルフオンラインで使ってみてやりやすかったので
本格的にNuxt.jsで作ってみました。
React.js(Next.js)やAngularもちょいちょい触ってみているのですが、
Vue.js(Nuxt.js)はやっぱりバランスがよくて個人開発にいいなぁという印象ですね。

月狼国さんが閉鎖らしいです

月狼国さん閉鎖

2月頭くらいの話だったかと思います。
トップページに記載されていますが、月狼国さんが2021年いっぱいで新規村作成ができなくなるようです。
トップページ - 月狼国side:M

いち人狼サイトの管理人としての考え

何回かプレイヤーとして利用させていただいたのと、
管理人同士ということで元副管理人のapricotさんと数回やりとりさせていただいた覚えがあります。
管理者としてきめ細やかな対応が評判通りで素晴らしいと思う反面、
負荷が物凄いのだろうなと心配しておりました。
(まだ今年中は管理されるとのことですが)本当にお疲れ様でした。

他サイトには失礼な話になりそうで申し訳ないのですが、
長期人狼(で自分で村を建てられる)サイトは、普段Twitterで聞こえてくる限りだと
三日月国さんと月狼国さんが2大サイトなのかなと思っていて、
役職豊富でお祭り騒ぎは三日月国さん、ガチは月狼国さんを利用する方が多い印象でした。
(RP向けの機能としてはどちらも豊富なんですかね?あまり詳しくなく。)

色々遊べる人狼サイトが多いので大丈夫かもしれませんが、
各サイトで同時に建てられる村の数が決まっているようですし、
長期人狼をやりたいという人が困らないとも限らないので、
私が管理するFIREWOLFについても今年中くらいには機能を色々増やしたりして、
多少なりとも受け皿となれたらと思っています。
うちを利用していただける人が増えるなら私も嬉しいですしね。
(いちおうRP向けではなくガチ向けではあるので、そちらの機能面が中心になるとは思います )

ちなみにまだあまり利用されてないこともありトラブル慣れしてないので微妙なのですが、
管理方針については頑張らないと思います。
基本的に村内で解決してもらうし、「うちのサイト利用してほしくないなぁ」と思ったら気分でBANしたりするんじゃないかなと。
利用者が増えてから考えればいいですねきっと。

LASTWOLFを公開しました

LASTWOLF

書き忘れてました
2020/12/16にLASTWOLFを公開しました。

特徴

自分で短期人狼の村を作成して無料で遊べるサイトです。
(HOWLING WOLFとFIREWOLFは長期人狼なので同じ人狼でもテイストがだいぶ違います)

認知度が低いので友人同士で誘い合わないと人数は揃わなそうですが、
お誘い合わせの上遊んでいただければ嬉しいです。