- 投稿日:2020-09-13T23:52:48+09:00
AtCoder Beginner Contest 178のメモ
前置き
Atcoderをやってみたので、自分用のメモです。
あとから加筆・修正する予定です。問題
https://atcoder.jp/contests/abc176
A
Q_A.gopackage main import ( "fmt" ) func main() { var x int fmt.Scanf("%d", &x) var c int if x == 0{ c = 1 } else { c = 0 } fmt.Printf("%d\n", c) }B
Q_B.gopackage main import ( "fmt" ) func main() { var a, b, c, d int fmt.Scanf("%d %d %d %d", &a, &b, &c, &d) var ac, ad, bc, bd int ac = a * c ad = a * d bc = b * c bd = b * d var ans int if ac < ad{ ans = ad } else { ans = ac } if ans < bc{ ans = bc } if ans < bd{ ans = bd } fmt.Printf("%d\n", ans) }C
Q_C.gopackage main import ( "fmt" ) func main() { var n int fmt.Scanf("%d", &n) var ans int64 = 0 if n<2{ ans = 0 } else { var t int64 = 1 var u int64 = 1 var s int64 = 1 for i:= 0; i < n; i++{ s = (s * 9) % 1000000007 t = (t * 8) % 1000000007 u = (u * 10) % 1000000007 } ans = u - s - s + t } ans = ans % 1000000007 fmt.Printf("%d\n", ans) }D
覚えてたら後で書きます。
E
覚えてたら後で書きます。
F
覚えてたら後で書きます。
- 投稿日:2020-09-13T20:10:37+09:00
同じ色のPodが4個くっついたらdeleteされるcustom controller「くべくべ」を作った
Kubernetes使ってると、Nodeにえらい数のPodが溜まってくじゃないですか。消したくなりますよね。連鎖してほしいですよね。なりません?なので、4つ同じ色のPodが4個くっついたらdeleteされる、爽快感のあるカオスエンジニアリング用のcustom contollerを作りました。
deleteされるだけでは寂しいので、deleteされていく様子を見るためのkubectl pluginも作りました。合わせて使うとこんな感じになります。
左側の●のひとつひとつがPodです。Nodeが列に対応してます。6Node構成です。各色8個ずつpodを立てていて、右側にreplicasetの増減を置いてみました。
レポジトリはこちらです。
https://github.com/omakeno/kubectl-kbkb
https://github.com/omakeno/kbkb-controller使い方と実装を説明します。使用は自己責任でお願いします。
動作
横軸をNode、縦軸をPodとして二次元のフィールドにPodが配置されてると見立てて動作します。それぞれcreationTimestampでソートされています。
特定のAnnotationからPodの色を判定し、周囲との隣接数を数えて必要数を超えていたら、それらのPodを削除します。AnnotationがないPodは白と判定されます。白はくっついても消えないですし、周りの色Podに巻き込まれて消えることもありません。
Annotationは
kbkb.k8s.omakenoyouna.net/color
です。Podごとに設定してください。red
,green
,yellow
,blue
,purple
が使えます。metadata: annotations: kbkb.k8s.omakenoyouna.net/color: blueAnnotationがPodに設定されていると、
kubectl kbkb
で色がついた状態で表示されます。bashでしか試していません。
等幅フォント前提です。-L
オプションで見やすく全角表示することができます。
-w
オプションでwatchできます。Podが消えていく様を見ることができます。
使い方
kbkb-controller
kbkb-controllerと関連オブジェクトをdeployします。
kubectl apply -f https://raw.githubusercontent.com/omakeno/kbkb-controller/master/deploy/deploy.yaml
適用したいnamespace内にkbkbオブジェクトを作成します。
下記は「4個消し」ですが、「2個消し」「6個消し」などの設定も可能です。apiVersion: k8s.omakenoyouna.net/v1beta1 kind: Kbkb metadata: name: kbkb-four spec: kokeshi: 4これだけです。
kubectl kbkb
バイナリを落として、pathが通るようにしてください。
kubectlのpluginの機構で、kubectl-xxxxにpathが通っていると、kubectl xxxx
のようにサブコマンドとして使えます。wget https://github.com/omakeno/kubectl-kbkb/releases/download/v0.2.3/kubectl-kbkb chmod +x kubectl-kbkb sudo cp kubectl-kbkb <your-path>あとは叩くだけです。
kubectl kbkb
--watch,-w
,--namespace,-n
,--large,-L
,--kubeconfig
のオプションがあります。実装
ここでは詳細な解説はせず、紹介程度にします。コード量も少ないので、気になる方はリポジトリを見てみてください。
別途記録用に記事を書くかもです。kbkb-controller
いわゆるカスタムコントローラーです。podをwatchして、4つ隣接した同色のPodを見つけてdeleteします。
リポジトリはここです。
https://github.com/omakeno/kbkb-controllerOperator SDKをgolangで利用しています。Tutorialに沿って進めばめっちゃ簡単です。
https://sdk.operatorframework.io/初心者でもTutorialに沿って進めばほぼほぼ完成されたコードを吐き出してくれるので、
Reconcile
のfunctionだけ実装すれば動きます。この1個の関数だけに処理をゴリゴリ書いています。かんたん。
それ以外のコードはほとんど自動生成されたものをそのまま使っているだけです。func (r *KbkbReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { ctx := context.Background() reqLogger := r.Log.WithValues("pod", req.NamespacedName) reqLogger.Info("Reconciling") // reconcileされたオブジェクトのNamespace listOption := &client.ListOptions{ Namespace: req.Namespace, } // 同一Namespace内のkbkbを取得 kbkbList := &k8sv1beta1.KbkbList{} if err := r.Client.List(ctx, kbkbList, listOption); err != nil { reqLogger.Error(err, "failed to get kbkb") return ctrl.Result{}, err } if len(kbkbList.Items) == 0 { reqLogger.Info("kbkb not found. Ignore not found") return ctrl.Result{}, nil } kbkbObj := kbkbList.Items[0] kokeshi := *(kbkbObj.Spec.Kokeshi) // Pod一覧とNode一覧を取得 podList := &corev1.PodList{} if err := r.Client.List(ctx, podList, listOption); err != nil { reqLogger.Error(err, "failed to get list of pods") return ctrl.Result{}, err } nodeList := &corev1.NodeList{} if err := r.Client.List(ctx, nodeList); err != nil { reqLogger.Error(err, "failed to get list of nodes") return ctrl.Result{}, err } // 隣接判定 kf := kbkb.BuildKbkbFieldFromList(podList, nodeList) if !kf.IsStable() { reqLogger.Info("All containers are not Ready.") return ctrl.Result{}, nil } erasablePods := kf.ErasableKbkbPodList(kokeshi) //podの削除 for _, kp := range erasablePods { pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Namespace: kp.ObjectMeta.Namespace, Name: kp.ObjectMeta.Name, }, } if err := r.Client.Delete(ctx, pod); err != nil { reqLogger.Error(err, "failed to delete pod", "deleteing pod", pod.ObjectMeta.Name) } else { reqLogger.Info("suceeded to delete pod", "deleted pod", pod.ObjectMeta.Name) } } return ctrl.Result{}, nil }隣接判定は別パッケージに切り出しました。以前みた実装を参考にしつつ、今回は深さ優先探索で判定してます。
https://github.com/omakeno/kbkbfunc (kf *KbkbField) ErasableKbkbPodList(kokeshi int) []*KbkbPod { checkedPods := []*KbkbPod{} erasablePods := []*KbkbPod{} for x, col := range *kf { for y, _ := range col.kbkbs { var neighborPods []*KbkbPod neighborPods, checkedPods = kf.getNeighbors(x, y, checkedPods) if len(neighborPods) >= kokeshi { erasablePods = append(erasablePods, neighborPods...) } } } return erasablePods } // 再帰で深さ優先探索する関数 func (kf *KbkbField) getNeighbors(x int, y int, checkedPods []*KbkbPod) (neighborPods, checkedPodsAfter []*KbkbPod) { p := kf.GetKbkbPod(x, y) neighborPods = []*KbkbPod{p} if contains(checkedPods, p) { checkedPodsAfter = checkedPods return } checkedPodsAfter = append(checkedPods, p) if p.Color() == "white" { return } // 上下左右のpodを走査 neighborPos := [][]int{ {x + 1, y}, {x - 1, y}, {x, y + 1}, {x, y - 1}, } for _, pos := range neighborPos { if np := kf.GetKbkbPod(pos[0], pos[1]); np != nil && !contains(checkedPodsAfter, np) && np.Color() == p.Color() { var neighborPodsHere []*KbkbPod neighborPodsHere, checkedPodsAfter = kf.getNeighbors(pos[0], pos[1], checkedPodsAfter) neighborPods = append(neighborPods, neighborPodsHere...) } } return }というわけで作りは簡単、とはいえ
controller-runtime
などのパッケージは使えないといけないですし、kubebuilderのマーカーについても抑えとかなきゃです。私は@go_vargoさんの書籍で一通り学習しました。やりたいことはこの中で全部書いてありました。
https://booth.pm/ja/items/1566979書籍の中でoperator-sdkの解説もありますが、現在はバージョンが上がっていて、コマンド体系も変わっているので注意が必要です。
またgolang自体も初心者だったので、プログラミング言語Go完全入門にもお世話になりました。
https://drive.google.com/file/d/1fLlg3Xw7CV680GQ65WkjxU5qX-PsApJg/viewkubectl-kbkb
kubectlのプラグインです。単なるシングルバイナリのCLIツールです。
4個くっついたらdeleteされたくなるような見た目でpodを表示することができます。
Krewに入れてもらうのはさすがに無理かなあと思って諦めてます。Krewもカスタムのリポジトリが使えるようになったみたいなので、こういうネタツールでも入れてくれるリポジトリがあったらいいなあ。リポジトリはここです。
https://github.com/omakeno/kubectl-kbkbこちらはあまりまとまった情報がなかったのですが、下記をベースにいじって出来ました。
https://github.com/kubernetes/sample-cli-plugincobraをシンプルに使います。cli-runtimeを使うと良いらしいのですが、今回は使ってません。それでも
o.Execute
を実装すればcliツールが簡単に作れます。
あとは公式パッケージであるclient-goの使い方さえわかれば、controller同様に書けます。golangは入力補完でなんとかなりますね。その分だけドキュメントは弱めですが。ちょっとだけ抜粋して載せます。
func CreateCmd() *cobra.Command { // コマンドを定義 o := NewKbkbOptions() var rootCmd = &cobra.Command{ Use: "kbkb [flags]", Short: "Show pods as kbkb format.", Example: fmt.Sprintf(kbkbExample, "kubectl"), SilenceUsage: true, RunE: func(c *cobra.Command, args []string) error { if err := o.Execute(c, args); err != nil { return err } return nil }, } // オプションをフラグとして設定 rootCmd.PersistentFlags().StringVarP(&o.namespace, "namespace", "n", "default", "specify namespace to show as kbkb format.") rootCmd.PersistentFlags().BoolVarP(&o.watch, "watch", "w", false, "watch kbkb") rootCmd.PersistentFlags().StringVarP(&o.kubeconfig, "kubeconfig", "", filepath.Join(homeDir(), ".kube", "config"), "(optional) absolute path to the kubeconfig file") rootCmd.PersistentFlags().BoolVarP(&o.large, "large", "L", false, "view on large size") return rootCmd } func (o *KbkbOptions) Execute(cmd *cobra.Command, args []string) error { config, err := clientcmd.BuildConfigFromFlags("", o.kubeconfig) if err != nil { panic(err.Error()) } clientset, err := kubernetes.NewForConfig(config) if err != nil { panic(err.Error()) } if o.watch { o.Watch(clientset) } else { o.Get(clientset) } return nil } func (o *KbkbOptions) Get(clientset *kubernetes.Clientset) { // pod, nodeの一覧取得 podList, err := clientset.CoreV1().Pods(o.namespace).List(context.TODO(), metav1.ListOptions{}) if err != nil { panic(err.Error()) } nodeList, err := clientset.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{}) if err != nil { panic(err.Error()) } // 描画処理(kbkbパッケージを使うだけ) kf := kbkb.BuildKbkbFieldFromList(podList, nodeList) writer := bashoverwriter.GetBashoverwriter() var kcs kbkb.KbkbCharSet if o.large { kcs = kbkb.GetKbkbCharSetWide() } else { kcs = kbkb.GetKbkbCharSet() } kcs.PrintKbkb(&writer, kf) }肝心の出力のフォーマットも、kbkbパッケージに寄せちゃってるので、こっちは至ってシンプルです。
まとめ
というわけで、削除処理と可視化はできました。
client-goやcontroller-runtimeやらのパッケージにkubebuilderやらoperator-sdkやら、すごく綺麗に整理されているので本当に簡単に実装が出来ます。ありがたい。さて、まだやりたいことが残ってます。
せっかくCNDT2020で話を聞いたのでOperator Lifecycle Managerとかで使えるようにしてみるのもいいのですが、まずは機能。
- 全てのPodがRunningになるとPodを2つ生成してくれるCustom Controller
- createされるpodにランダムでAnnotationを付与するAdmission Controller
- Queuingされるpodを2個ずつ操作して手動でnodeにschedulingするCustom Scheduler
2つずつ生成されるpodを上から落として積み上げて行きたくなりますよね。
めざせ19連鎖!
- 投稿日:2020-09-13T20:10:37+09:00
kubernetesでもぷよぷよがしたいので同じ色のPodが4個くっついたらdeleteされるcustom controller「くべくべ」を作った
Kubernetes使ってると、Nodeにえらい数のPodが溜まってくじゃないですか。消したくなりますよね。連鎖してほしいですよね。なりません?なので、4つ同じ色のPodが4個くっついたらdeleteされる、爽快感のあるカオスエンジニアリング用のcustom contollerを作りました。
deleteされるだけでは寂しいので、deleteされていく様子を見るためのkubectl pluginも作りました。合わせて使うとこんな感じになります。
左側の●のひとつひとつがPodです。Nodeが列に対応してます。6Node構成です。各色8個ずつpodを立てていて、右側にreplicasetの増減を置いてみました。
レポジトリはこちらです。
https://github.com/omakeno/kubectl-kbkb
https://github.com/omakeno/kbkb-controller使い方と実装を説明します。使用は自己責任でお願いします。
動作
横軸をNode、縦軸をPodとして二次元のフィールドにPodが配置されてると見立てて動作します。それぞれcreationTimestampでソートされています。
特定のAnnotationからPodの色を判定し、周囲との隣接数を数えて必要数を超えていたら、それらのPodを削除します。AnnotationがないPodは白と判定されます。白はくっついても消えないですし、周りの色Podに巻き込まれて消えることもありません。
Annotationは
kbkb.k8s.omakenoyouna.net/color
です。Podごとに設定してください。red
,green
,yellow
,blue
,purple
が使えます。metadata: annotations: kbkb.k8s.omakenoyouna.net/color: blueAnnotationがPodに設定されていると、
kubectl kbkb
で色がついた状態で表示されます。bashでしか試していません。
等幅フォント前提です。-L
オプションで見やすく全角表示することができます。
-w
オプションでwatchできます。Podが消えていく様を見ることができます。
使い方
kbkb-controller
kbkb-controllerと関連オブジェクトをdeployします。
kubectl apply -f https://raw.githubusercontent.com/omakeno/kbkb-controller/master/deploy/deploy.yaml
適用したいnamespace内にkbkbオブジェクトを作成します。
下記は「4個消し」ですが、「2個消し」「6個消し」などの設定も可能です。apiVersion: k8s.omakenoyouna.net/v1beta1 kind: Kbkb metadata: name: kbkb-four spec: kokeshi: 4これだけです。
kubectl kbkb
バイナリを落として、pathが通るようにしてください。
kubectlのpluginの機構で、kubectl-xxxxにpathが通っていると、kubectl xxxx
のようにサブコマンドとして使えます。wget https://github.com/omakeno/kubectl-kbkb/releases/download/v0.2.3/kubectl-kbkb chmod +x kubectl-kbkb sudo cp kubectl-kbkb <your-path>あとは叩くだけです。
kubectl kbkb
--watch,-w
,--namespace,-n
,--large,-L
,--kubeconfig
のオプションがあります。実装
ここでは詳細な解説はせず、紹介程度にします。コード量も少ないので、気になる方はリポジトリを見てみてください。
別途記録用に記事を書くかもです。kbkb-controller
いわゆるカスタムコントローラーです。podをwatchして、4つ隣接した同色のPodを見つけてdeleteします。
リポジトリはここです。
https://github.com/omakeno/kbkb-controllerOperator SDKをgolangで利用しています。Tutorialに沿って進めばめっちゃ簡単です。
https://sdk.operatorframework.io/初心者でもTutorialに沿って進めばほぼほぼ完成されたコードを吐き出してくれるので、
Reconcile
のfunctionだけ実装すれば動きます。この1個の関数だけに処理をゴリゴリ書いています。かんたん。
それ以外のコードはほとんど自動生成されたものをそのまま使っているだけです。func (r *KbkbReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { ctx := context.Background() reqLogger := r.Log.WithValues("pod", req.NamespacedName) reqLogger.Info("Reconciling") // reconcileされたオブジェクトのNamespace listOption := &client.ListOptions{ Namespace: req.Namespace, } // 同一Namespace内のkbkbを取得 kbkbList := &k8sv1beta1.KbkbList{} if err := r.Client.List(ctx, kbkbList, listOption); err != nil { reqLogger.Error(err, "failed to get kbkb") return ctrl.Result{}, err } if len(kbkbList.Items) == 0 { reqLogger.Info("kbkb not found. Ignore not found") return ctrl.Result{}, nil } kbkbObj := kbkbList.Items[0] kokeshi := *(kbkbObj.Spec.Kokeshi) // Pod一覧とNode一覧を取得 podList := &corev1.PodList{} if err := r.Client.List(ctx, podList, listOption); err != nil { reqLogger.Error(err, "failed to get list of pods") return ctrl.Result{}, err } nodeList := &corev1.NodeList{} if err := r.Client.List(ctx, nodeList); err != nil { reqLogger.Error(err, "failed to get list of nodes") return ctrl.Result{}, err } // 隣接判定 kf := kbkb.BuildKbkbFieldFromList(podList, nodeList) if !kf.IsStable() { reqLogger.Info("All containers are not Ready.") return ctrl.Result{}, nil } erasablePods := kf.ErasableKbkbPodList(kokeshi) //podの削除 for _, kp := range erasablePods { pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Namespace: kp.ObjectMeta.Namespace, Name: kp.ObjectMeta.Name, }, } if err := r.Client.Delete(ctx, pod); err != nil { reqLogger.Error(err, "failed to delete pod", "deleteing pod", pod.ObjectMeta.Name) } else { reqLogger.Info("suceeded to delete pod", "deleted pod", pod.ObjectMeta.Name) } } return ctrl.Result{}, nil }隣接判定は別パッケージに切り出しました。以前みた実装を参考にしつつ、今回は深さ優先探索で判定してます。
https://github.com/omakeno/kbkbfunc (kf *KbkbField) ErasableKbkbPodList(kokeshi int) []*KbkbPod { checkedPods := []*KbkbPod{} erasablePods := []*KbkbPod{} for x, col := range *kf { for y, _ := range col.kbkbs { var neighborPods []*KbkbPod neighborPods, checkedPods = kf.getNeighbors(x, y, checkedPods) if len(neighborPods) >= kokeshi { erasablePods = append(erasablePods, neighborPods...) } } } return erasablePods } // 再帰で深さ優先探索する関数 func (kf *KbkbField) getNeighbors(x int, y int, checkedPods []*KbkbPod) (neighborPods, checkedPodsAfter []*KbkbPod) { p := kf.GetKbkbPod(x, y) neighborPods = []*KbkbPod{p} if contains(checkedPods, p) { checkedPodsAfter = checkedPods return } checkedPodsAfter = append(checkedPods, p) if p.Color() == "white" { return } // 上下左右のpodを走査 neighborPos := [][]int{ {x + 1, y}, {x - 1, y}, {x, y + 1}, {x, y - 1}, } for _, pos := range neighborPos { if np := kf.GetKbkbPod(pos[0], pos[1]); np != nil && !contains(checkedPodsAfter, np) && np.Color() == p.Color() { var neighborPodsHere []*KbkbPod neighborPodsHere, checkedPodsAfter = kf.getNeighbors(pos[0], pos[1], checkedPodsAfter) neighborPods = append(neighborPods, neighborPodsHere...) } } return }というわけで作りは簡単、とはいえ
controller-runtime
などのパッケージは使えないといけないですし、kubebuilderのマーカーについても抑えとかなきゃです。私は@go_vargoさんの書籍で一通り学習しました。やりたいことはこの中で全部書いてありました。
https://booth.pm/ja/items/1566979書籍の中でoperator-sdkの解説もありますが、現在はバージョンが上がっていて、コマンド体系も変わっているので注意が必要です。
またgolang自体も初心者だったので、プログラミング言語Go完全入門にもお世話になりました。
https://drive.google.com/file/d/1fLlg3Xw7CV680GQ65WkjxU5qX-PsApJg/viewkubectl-kbkb
kubectlのプラグインです。単なるシングルバイナリのCLIツールです。
4個くっついたらdeleteされたくなるような見た目でpodを表示することができます。
Krewに入れてもらうのはさすがに無理かなあと思って諦めてます。Krewもカスタムのリポジトリが使えるようになったみたいなので、こういうネタツールでも入れてくれるリポジトリがあったらいいなあ。リポジトリはここです。
https://github.com/omakeno/kubectl-kbkbこちらはあまりまとまった情報がなかったのですが、下記をベースにいじって出来ました。
https://github.com/kubernetes/sample-cli-plugincobraをシンプルに使います。cli-runtimeを使うと良いらしいのですが、今回は使ってません。それでも
o.Execute
を実装すればcliツールが簡単に作れます。
あとは公式パッケージであるclient-goの使い方さえわかれば、controller同様に書けます。golangは入力補完でなんとかなりますね。その分だけドキュメントは弱めですが。ちょっとだけ抜粋して載せます。
func CreateCmd() *cobra.Command { // コマンドを定義 o := NewKbkbOptions() var rootCmd = &cobra.Command{ Use: "kbkb [flags]", Short: "Show pods as kbkb format.", Example: fmt.Sprintf(kbkbExample, "kubectl"), SilenceUsage: true, RunE: func(c *cobra.Command, args []string) error { if err := o.Execute(c, args); err != nil { return err } return nil }, } // オプションをフラグとして設定 rootCmd.PersistentFlags().StringVarP(&o.namespace, "namespace", "n", "default", "specify namespace to show as kbkb format.") rootCmd.PersistentFlags().BoolVarP(&o.watch, "watch", "w", false, "watch kbkb") rootCmd.PersistentFlags().StringVarP(&o.kubeconfig, "kubeconfig", "", filepath.Join(homeDir(), ".kube", "config"), "(optional) absolute path to the kubeconfig file") rootCmd.PersistentFlags().BoolVarP(&o.large, "large", "L", false, "view on large size") return rootCmd } func (o *KbkbOptions) Execute(cmd *cobra.Command, args []string) error { config, err := clientcmd.BuildConfigFromFlags("", o.kubeconfig) if err != nil { panic(err.Error()) } clientset, err := kubernetes.NewForConfig(config) if err != nil { panic(err.Error()) } if o.watch { o.Watch(clientset) } else { o.Get(clientset) } return nil } func (o *KbkbOptions) Get(clientset *kubernetes.Clientset) { // pod, nodeの一覧取得 podList, err := clientset.CoreV1().Pods(o.namespace).List(context.TODO(), metav1.ListOptions{}) if err != nil { panic(err.Error()) } nodeList, err := clientset.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{}) if err != nil { panic(err.Error()) } // 描画処理(kbkbパッケージを使うだけ) kf := kbkb.BuildKbkbFieldFromList(podList, nodeList) writer := bashoverwriter.GetBashoverwriter() var kcs kbkb.KbkbCharSet if o.large { kcs = kbkb.GetKbkbCharSetWide() } else { kcs = kbkb.GetKbkbCharSet() } kcs.PrintKbkb(&writer, kf) }肝心の出力のフォーマットも、kbkbパッケージに寄せちゃってるので、こっちは至ってシンプルです。
まとめ
というわけで、削除処理と可視化はできました。
client-goやcontroller-runtimeやらのパッケージにkubebuilderやらoperator-sdkやら、すごく綺麗に整理されているので本当に簡単に実装が出来ます。ありがたい。さて、まだやりたいことが残ってます。
せっかくCNDT2020で話を聞いたのでOperator Lifecycle Managerとかで使えるようにしてみるのもいいのですが、まずは機能。
- 全てのPodがRunningになるとPodを2つ生成してくれるCustom Controller
- createされるpodにランダムでAnnotationを付与するAdmission Controller
- Queuingされるpodを2個ずつ操作して手動でnodeにschedulingするCustom Scheduler
2つずつ生成されるpodを上から落として積み上げて行きたくなりますよね。
めざせ19連鎖!
- 投稿日:2020-09-13T18:32:37+09:00
A Tour of Go メモ 【3】1日目
ポインタ、 構造体、 配列、 スライス
ポインタ
func main() { i, j ;= 10, 200 # pはiの値を参照する p := &i # iとpのアドレスを取得する fmt.Println(&i) fmt.Println(&p) # pが参照している変数、つまり、iを表示する fmt.Println(*p) # pが参照する変数に21を代入する。つまり、i に 21 を代入する *p = 21 fmt.Println(i) # pはjの値を参照する p = &j # pが参照する値に”pが参照する値を100で割った値”を代入する。 # つまり、jに"jを100で割った値"を代入する *p = *p / 100 fmt.Println(j) } // iを参照したpの値 >10 // *pに21を代入した後のi >21 // iのアドレス >0xc000100010 // pのアドレス iを参照して値は同じだが、アドレスは違う >0xc000102018 >2構造体
struct(構造体)はフィールド(field) の集まり
type Vertex struct { X int Y int } func main() { fmt.Println(Vertex{1, 2}) v := Vertex{1, 2} fmt.Println(v.X) v.X = 4 fmt.Println(v.X) } > {1, 2} > 1 > 4構造体とポインタ
type Vertex struct { X int Y int } func main() { v := Vertex{1, 2} fmt.Println(v) # pはv(Vertex)を参照する p := &v # pが参照しているVertexのXに値を代入する p.X = 1e9 fmt.Println(v) } > {1 2} // ポインタを通して、Xの値が再代入されている > {1000000000 2}Struct リテラル
type Vertex struct { X, Y int } var ( v1 = Vertex{1, 2} # Xには1を、Yは0(何も代入されていないので、初期値の0になる) v2 = Vertex{X: 1} # X にも Y にも何も代入されていないので、初期値の0になる v3 = Vertex{} # ちょっとよくわからない・・・ p = &Vertex{1, 2} ) func main() { fmt.Println(v1, p, v2, v3) } > {1 2} &{11 2} {1 0} {0 0}配列
拡張不可なので、append関数で要素を追加できない
func main() { var a [2]string a[0] = "Hello" a[1] = "World" fmt.Println(a[0], a[1]) fmt.Println(a) primes := [6]int{1,2,3,4,5,6} fmt.Println(primes) } > Hello World > [Hello World] > [2 3 5 7 11 13]スライス
append関数で要素の追加が可能、つまり、拡張可能
```
func main() {
primes := [6]int{2, 3, 5, 7, 11, 13}var s []int = primes[1:4] fmt.Println(s) fmt.Println(primes[:1]) fmt.Println(primes[2:])}
[3 5 7]
[2 3 ]
[5 7 11 13]
- スライスした後の配列に値を代入すると元の配列が変わる
func main() {
names := [4]string{
"John",
"Paul",
"George",
"Ringo",
}
fmt.Println(names)a := names[0:2] b := names[1:3] fmt.Println(a, b) b[0] = "XXX" fmt.Println(a, b) fmt.Println(names) fmt.Println(names)}
[John Paul George Ringo]
[John Paul] [Paul George]
[John XXX] [XXX George]
[John XXX George Ringo]
//元の配列が変わっている
[John XXX George Ringo]
```スライス リテラル
func main() { q := []int{2, 3, 4, 5, 7, 11, 13} fmt.Println(q) r := []bool{true, false, true, true, false, true} fmt.Println(r) s := []struct{ i int b bool }{ {2, true} {3, false}, {5, true}, {7, true}, {11, false}, {13, true} } fmt.Println(s) } > [2 3 5 7 11 13] > [true false true true false true] > [{2 true} {3 false} {5 true} {7 true} {11 false} {13 true}]スライスのlengthとcapacity
length ・・・ 実際に配列に入っている要素の数
capacity ・・・ 確保されているメモリの領域
*配列とスライスは全くの別物
Go言語のスライスで勘違いしやすいこところfunc main() { s := []int{2, 3, 5, 7, 11, 13} printSlice(s) // Slice the slice to give it zero length. s = s[:5] printSlice(s) // Extend its length. s = s[:6] printSlice(s) // Drop its first two values. s = s[2:] printSlice(s) s = s[:5] printSlice(s) s = append(s, 4) printSlice(s) } func printSlice(s []int) { fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) } > len=6 cap=6 [2 3 5 7 11 13] //要素を一つ削る、しかし、capacityは減っていない > len=5 cap=6 [2 3 5 7 11] //長さを戻すと削ったはずの要素が復活する > len=6 cap=6 [2 3 5 7 11 13] // ドロップするとlengthもcapacityも減っている > len=4 cap=4 [5 7 11 13] // capacity以上にlengthを伸ばそうとするとエラーになる > panic: runtime error: slice bounds out of range [:5] with capacity 4 // capacityを超えて要素を追加しようとすると、capacityが以前の2倍になる > len=5 cap=8 [5 7 11 13 4] #sliceの初期値 func main() { var s []int fmt.Println(s, len(s), cap(s)) if s == nil { fmt.Println("nil!") } } > [] 0 0 > nil!スライスとmake
makeを使って、lengthとcapacityを指定して、スライスを生成できる
要素の初期値は0func main() { a := make([]int, 5) printSlice("a", a) b := make([]int, 0, 5) printSlice("b", b) c := b[:2] printSlice("c", c) d := c[2:5] printSlice("d", d) } func printSlice(s string, x []int) { fmt.Printf("%s len=%d cap=%d %v\n", s, len(x), cap(x), x) }スライスの中にスライス
import ( "fmt" // 文字列の操作 "strings" ) func main() { // Create a tic-tac-toe board. board := [][]string{ []string{"_", "_", "_"}, []string{"_", "_", "_"}, []string{"_", "_", "_"}, } fmt.Println(board) board[0][0] = "X" board[2][2] = "O" board[1][2] = "X" board[1][0] = "O" board[0][2] = "X" for i := 0; i < len(board); i++ { // スライス内の要素を" "でつないで出力 fmt.Printf("%s\n", strings.Join(board[i], " ")) } } > [[_ _ _] [_ _ _] [_ _ _]] > X _ X > O _ X > _ _ O
- 投稿日:2020-09-13T11:55:57+09:00
Go言語: 雑多な Tips、文法など
この記事について
忘備録として Go言語の雑多なTIPS、文法などを脈絡なくまとめていきます。
TIPS
ランダムな文字列の生成
いろんな方法があるが、長さや文字種の指定がなければこんな簡易的な方法もある(Unix時間を36進数表記の文字列に変換してる)。
s := strconv.FormatInt(time.Now().UnixNano(), 36) // 実行結果は、例えば "c3av0t23ntsk"ただ、時刻を用いてるだけなので複数の Goroutine から同時実行すると同じ文字列が返る可能があることは注意。
ある文字列が数字だけ含むかチェック
"20200601" のように、文字列が数字だけ含むかどうかチェックしたいときは
strings.Trim(<文字列>, "0123456789") == ""
が使える。サンプルコード:
func main() { params := []string {"1", "dog", "20200601", "5c6"} for _, p := range params { if strings.Trim(p, "0123456789") == "" { fmt.Println(p) } } } // 実行結果 // 1 // 20200601if の代わりの switch
switch の後ろに変数を指定しないで、単に if 文の代わりにのように使うことが出来る。
type user struct { name string age int healthy bool hobbies []string } func main() { u := user{ name: "Andy", age: 110, healthy: true, hobbies: []string{"Game", "Music"}, } // if, else の代わり switch { case u.name == "": fmt.Println("Unknown!") case u.age > 100 && u.healthy: fmt.Println("Fantastic!") case len(u.hobbies) > 0: fmt.Println("Have fun!") } } // 実行結果 // Fantastic!main 関数を抜けたくない時
goroutineの開始後に main関数から抜けたくない時、select を使うことができる。
func main() { // goroutineを開始... // goroutineがずっと動いてるので、main から抜けたくない select {} }適当なサンプルだがこんな感じの使い方。goroutineが終了するケースでは
all goroutines are asleep - deadlock!
って怒られるので要注意。package main import ( "fmt" "net/http" ) func main() { // 複数の HTTP サーバーを起動 addrs := []string{":8080", ":8081", ":8082"} for _, addr := range addrs { s := &http.Server{ Addr: addr, Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello from %s", r.Host) }), } go s.ListenAndServe() } select {} }main 関数を抜けたくない時 (ユーザーの入力待ち状態にする)
似た話だが、mainを抜けたくないため入力待ち状態にする方法。ENTER キーを打つと main を抜ける。
import ( "bufio" "os" ) func main() { // goroutineを開始... // 入力待ち状態にする bufio.NewScanner(os.Stdin).Scan() }スライスの要素を削除
よくあるやつだが、スライスの途中の要素を削除する方法。
func main() { s := []string{"dog", "cat", "bird", "desk", "pig", "rabbit"} n := 3 // "desk" を削除したい s = append(s[:n], s[n+1:]...) fmt.Println(s) // append を使わずにこうやってもよい。 //copy(s[n:], s[n+1:]) //s = s[:len(s)-1] } // 実行結果 // [dog cat bird pig rabbit]ある変数がインターフェースを実装しているかチェック
reflect パッケージの Implements を使う。
package main import ( "fmt" "reflect" ) type Sender interface { Send() } type Receiver interface { Recv() } // App は Sender インターフェースだけを実装 type App struct{} func (c *App) Send() { fmt.Println("Send something") } func main() { a := &App{} at := reflect.TypeOf(a) st := reflect.TypeOf((*Sender)(nil)).Elem() rt := reflect.TypeOf((*Receiver)(nil)).Elem() fmt.Printf("Client implements Sender : %v\n", at.Implements(st)) fmt.Printf("Client implements Receiver : %v\n", at.Implements(rt)) } // 実行結果: // Client implements Sender : true // Client implements Receiver : false文法
make してない map へのアクセス
make してない map(つまりnil) のキーにアクセスすることが出来て、値として定義した型の初期値が返ってくる。
type user struct { name string age int } func main() { // map を宣言 var m map[string]user // m は nil のまま if m == nil { fmt.Println("m is nil") } // キーにアクセスすると、値として定義した型(user)の初期値が返る u := m["AAA"] fmt.Printf("%#v\n", u) // ただし、Key に Value をセットしようとすると panic する // -> panic: assignment to entry in nil map //m["BBB"] = user{name: "Andy", age: 20} } // 実行結果 // m is nil // main.user{name:"", age:0}型エイリアス
type構文と見た目が似てるが、型のエイリアスを定義する alias 構文というものがある。違いは以下。
// type構文 : NewString と string は異なる型として扱われる type NewString string // alias構文 : AliasString と string 同じ型として扱われる type AliasString = stringサンプルコード:
type NewString string type AliasString = string func main() { s := "Hello" // 型が違うのでコンパイルエラーになる // → cannot use s (type string) as type NewString in assignment var ns NewString = s // こっちは OK var as AliasString = s fmt.Printf("%v\n", as) }ループ変数
Go のハマりどころのド定番。
Go ではループ変数(下記コードの n)はループが回っている間ずっと単一の変数、つまり、同じメモリ領域を使っている。ループが回るごとに変数の値が変わるだけで、変数(メモリ領域)そのものは同じ。なので、以下のようにループ変数のアドレスを取得すると意図しない動作になる。
func main() { iNums := []int {1, 2, 3} var oNums []*int // ループ変数 n のアドレスを oNums に追加していく for _, n := range iNums { oNums = append(oNums, &n) } // oNums に追加された値とアドレスを確認 for i, pn := range oNums { fmt.Printf("Index [%d], Value [%d], Address [%p]\n", i, *pn, pn) } } // 実行結果: Value は 1, 2, 3 にはならない。 // Index [0], Value [3], Address [0xc00002c008] // Index [1], Value [3], Address [0xc00002c008] // Index [2], Value [3], Address [0xc00002c008]この場合、こんな風に対処できる(一部のみ掲載)。
for _, n := range iNums { N := n oNums = append(oNums, &N) } // 実行結果: Index [0], Value [1], Address [0xc00002c008] Index [1], Value [2], Address [0xc00002c040] Index [2], Value [3], Address [0xc00002c048]ループ変数と goroutine
Go のハマりどころのド定番 その2。
以下のコードは単一のループ変数 n を複数の goroutine が参照していること、かつ、n は goroutine 実行時の値として評価されるため意図した動作にならない。goroutine の実行タイミングによるが、ループが回り終わった後に実行されるとしたら全て最終要素の 3 になる。iNums := []int{1, 2, 3} for _, n := range iNums { go func() { fmt.Println(n) }() } // 実行結果: 1, 2, 3 にはならない // 3 // 3 // 3対処方法は、goroutine 定義時の値を引数として渡してあげること。
for _, n := range iNums { go func(N int) { fmt.Println(N) }(n) }これでもよい。
for _, n := range iNums { N := n go func() { fmt.Println(N) }() }クロージャー
クロージャーとは通常の関数とは少し異なり、関数の定義だけでなく、それが定義された際の環境(自身の外で宣言された変数)をセットしたもの。
以下、簡単な例。
// getFuncはクロージャーを生成する関数 func getFunc() func() { i := 0 // 以下の func() がクロージャー return func() { // 自身の外で宣言された i にアクセスする i++ fmt.Println(i) } } func main() { // クロージャーの生成と呼び出し(1つ目) : 1 → 2 → 3 と増える fn1 := getFunc() fn1() fn1() fn1() // クロージャーの生成と呼び出し(2つ目): また 1 から始まる fn2 := getFunc() fn2() } // 実行結果 // 1 // 2 // 3 // 1ここで1つ前の「ループ変数と goroutine」の例を見てみる。
goroutine として実行される関数は、自身の外で使われてるループ変数
n
を参照するクロージャー。ループ変数(この例のn
)は全て同じメモリ領域を指すので、この例の3つの goroutine は全て同じ変数n
を参照する。各 goroutine の実行タイミングによるが、すべての出力が 3 になったりする。解決方法は前述のとおり goroutine の引数としてn
を渡すなど。iNums := []int{1, 2, 3} for _, n := range iNums { go func() { fmt.Println(n) }() } // 実行結果(タイミングによる) // 3 // 3 // 3(参考)
- https://golang.org/doc/faq#closures_and_goroutines
- https://golang.org/doc/effective_go.html#goroutines
- https://gobyexample.com/closuresコンパイラ
Heap と Stack
Go の変数はメモリ上の Heap と Stack のどちらに保存されるか?
- コンパイラが自動で判断して、関数内からのみ参照される変数ならスタック、関数外から参照される可能性がある変数はヒープに割り当てる
- ビルド時に
go build -gcflags -m main.go
というように-gcflags
オプションを付けると、どちらが選択されたか見れる。(引用) https://golang.org/doc/faq
From a correctness standpoint, you don't need to know. Each variable in Go exists as long as there are references to it. The storage location chosen by the implementation is irrelevant to the semantics of the language
The storage location does have an effect on writing efficient programs. When possible, the Go compilers will allocate variables that are local to a function in that function's stack frame. However, if the compiler cannot prove that the variable is not referenced after the function returns, then the compiler must allocate the variable on the garbage-collected heap to avoid dangling pointer errors. Also, if a local variable is very large, it might make more sense to store it on the heap rather than the stack.
- 投稿日:2020-09-13T09:53:38+09:00
【Go】http.HandleFunc()とhttp.Handle()の使い分けは構造体のフィールドを使うかどうか
はじめに
http.HandleFunc()とhttp.Handle()でServeHTTPを呼んでいるのに
なにが違っているのか分からなかったので調べてみました。結論
構造体のフィールドを使いたいかどうか
http.HandleFunc()とhttp.Handle()
ListenAndServe starts an HTTP server with a given address and handler. The handler is usually nil, which means to use DefaultServeMux. Handle and HandleFunc add handlers to DefaultServeMux:
とどちらもDefaultServeMuxのパターンとしてハンドラを登録するもの。
DefaultServeMuxについてやハンドラとの関係性については以下の記事がわかりやすいです。
【Go】net/httpパッケージを読んでhttp.HandleFuncが実行される仕組みhttp.HandleFunc()
ドキュメントの例では
package main import ( "io" "log" "net/http" ) func main() { h1 := func(w http.ResponseWriter, _ *http.Request) { io.WriteString(w, "Hello from a HandleFunc #1!\n") } h2 := func(w http.ResponseWriter, _ *http.Request) { io.WriteString(w, "Hello from a HandleFunc #2!\n") } http.HandleFunc("/", h1) http.HandleFunc("/endpoint", h2) log.Fatal(http.ListenAndServe(":8080", nil)) }という形で
/
のパターンであればHello from a HandleFunc #1!
を出力し
/endpoint
のパターンであればHello from a HandleFunc #2!
を出力するhttp.Handle()
ドキュメントの例では
package main import ( "fmt" "log" "net/http" "sync" ) type countHandler struct { mu sync.Mutex // guards n n int } func (h *countHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.mu.Lock() defer h.mu.Unlock() h.n++ fmt.Fprintf(w, "count is %d\n", h.n) } func main() { http.Handle("/count", new(countHandler)) log.Fatal(http.ListenAndServe(":8080", nil)) }という形で
/count
のパターンであればカウントの回数を出力するこの時にstructのフィールド
n int
に呼ばれた回数ごとに+1しているので
呼ばれるたびに回数を増加することができる。まとめ
ServeHTTPを実装しhttp.Handle()を使うことでstruct内のフィールドを利用したハンドラ作成が可能となる。
参考文献
ありがとうございました。
GoのドキュメントのPackage http
【Go】net/httpパッケージを読んでhttp.HandleFuncが実行される仕組み
Go 言語の http パッケージにある Handle とか Handler とか HandleFunc とか HandlerFunc とかよくわからないままとりあえずイディオムとして使ってたのでちゃんと理解したメモ
- 投稿日:2020-09-13T08:56:43+09:00
GoでExcelを操作する
Goでエクセルファイルを操作する方法のメモです。
tealeg/xlsxライブラリを使うと簡単でした。Gitのインポート
github上のライブラリを使用するため、Gitのダウンロードが必要でした。
※設定はデフォルトのままで行いました。ライブラリのインポート
コマンドプロンプトで以下のコマンドを実行すると、エクセルのライブラリが使えるようになります。go get の後のURIはGoDocで検索して取得しました。
go get github.com/tealeg/xlsxコマンドで実行するだけなので楽でした。
プログラム作成
テスト用として、エクセルにシートを追加するプログラムを作成しました。
<手順>
1. goファイルと同階層に新規ワークシートを作成
2. ファイル名を「テスト」とリネームして保存
3. 以下プログラムを実行test.gopackage main import ( "fmt" "time" "github.com/tealeg/xlsx" ) func main() { dt1 := time.Now() excel, _ := xlsx.OpenFile("テスト.xlsx") excel.AddSheet("GOで作成") excel.Save("./new.xlsx") dt2 := time.Now() fmt.Println(dt2.Sub(dt1)) // 実行時間 }実行すると、「テスト.xlsx」のコピーにシートが追加された状態で新しいファイル「new.xlsx」が同階層に保存されます。
※はじめは上書きをするプログラムを作成したのですが、PC内のセキュリティソフトに「疑わしい処理」としてブロックされてしまいました。上書きするには、エクセルシートのセキュリティの設定を下げる必要がありそうです。プログラムの実行時間は12ミリ秒でした。
エクセルファイルを開いて、自身でシートを追加するより圧倒的に速いです。使用関数
tealeg/xlsxライブラリ内から以下の関数を使用しました。
func OpenFile(fileName string, options ...FileOption) (file *File, err error)
OpenFile will take the name of an XLSX file and returns a populated xlsx.
File struct for it. You may pass it zero, one or many FileOption functions that affect the behaviour of the file.func (f *File) AddSheet(sheetName string) (*Sheet, error)
AddSheet Add a new Sheet, with the provided name, to a File.
The minimum sheet name length is 1 character.
If the sheet name length is less an error is thrown.
The maximum sheet name length is 31 characters.
If the sheet name length is exceeded an error is thrown.
These special characters are also not allowed: : \ / ? * [ ]func (f *File) Save(path string) (err error)
Save the File to an xlsx file at the provided path.
今回はファイルやシートを扱う関数を使用しましたが、セルや列を扱う関数もあるようなので、
もう少し便利なプログラムを作れそうです。
▼参考にしたサイト
Go言語でExcelファイルを処理するのが超簡単だった