JavaScriptの勉強中:その7 Standard Library / Iterators and Generators

もう日課になってきた。

bufferings.hatenablog.com

## Ch. 11 The JavaScript Standard Library

  • JavaScript では Object を map や set のように使うこともできるし実際に使われてきた
  • けど、文字列に限定されるし "toString" みたいなプロパティもあってややこしい
  • なので ES6 では Set と Map クラスが導入された。

Set

  • 値の重複を持たないコレクション
  • Set は「順序を持たないコレクション」とよく言われるけど JavaScript の場合はちょっと違う。JavaScript の Set は要素が挿入された順番を覚えていて、iterate するのにその順番を使うから。

へー。日本語版の方には書いてないけど、英語版の方にはちゃんとそう書いてある:

Set - JavaScript | MDN

Set objects are collections of values. You can iterate through the elements of a set in insertion order. A value in the Set may only occur once; it is unique in the Set's collection.

  • 等価性のチェックには SameValueZero が使用される( https://tc39.es/ecma262/#sec-set-objects )。Array の includes() と同じだな。NaN もちゃんと判別されるってことね。
  • Set は iterable なので for/of ループや ... spread operator を使用することができる。
  • forEach() メソッドもある

Map

  • Map のキーの等価性チェックにも SameValueZero が使用される( https://tc39.es/ecma262/#sec-map-objects )
  • Map も iterable なので for/of ループや ... spread operator を使用することができる。
> let m = new Map([['x', 1], ['y', 2]]);
undefined

> m
Map(2) { 'x' => 1, 'y' => 2 }

> [...m]
[ [ 'x', 1 ], [ 'y', 2 ] ]

> [...m.keys()]
[ 'x', 'y' ]

> [...m.values()]
[ 1, 2 ]

> [...m.entries()]
[ [ 'x', 1 ], [ 'y', 2 ] ]

> for(let [key, value] of m) {
... console.log(`${ key }: ${ value }`);
... }
x: 1
y: 2
  • forEach() メソッドもある。ちょっと変だけど引数は (value, key) の順番になる:
matchMedia.forEach((value, key) => {
    // ...
});

配列を一般化したものとして考えてみると、インデックスが2番目の引数に来るのも分かる。

Error

  • JavaScript は何でも throw して catch することができる。プリミティブでも。(まじか・・・
  • でも普通は Error クラスかそのサブクラスを投げる
  • Error クラスは messagename の2つのプロパティと toString() メソッドを持っている
  • ES標準ではないけど、Nodeやブラウザでは stack プロパティがあって、スタックトレースを含む複数行文字列が格納される

URL

  • URL クラスはES標準ではないけど、IE以外のブラウザやNodeで利用できる
> new URL("https://example.com:8000/path/name?q=term#fragment");
URL {
  href: 'https://example.com:8000/path/name?q=term#fragment',
  origin: 'https://example.com:8000',
  protocol: 'https:',
  username: '',
  password: '',
  host: 'example.com:8000',
  hostname: 'example.com',
  port: '8000',
  pathname: '/path/name',
  search: '?q=term',
  searchParams: URLSearchParams { 'q' => 'term' },
  hash: '#fragment'
}

へー。便利そうだな。URL触りたいときには思い出そっと。

  • URL API が定義される前は escape() unescape()エスケープなどに使われていたけど非推奨になった。
  • その代わりに ES が導入したのは encodeURI() decodeURI()encoeURIComponent() decodeURIComponent()
  • これらのレガシー関数の問題は単一のエンコードを全体に適用するというところ。URLの部分によって異なるエンコードを用いている場合でも。
  • 解決策としては単純に URL クラスを使えばいい。

その他

WeakMap, WeakSet, Typed Arrays, Regular Expressions, Date, JSON, Intl, Console API とか色々あるけど、必要になったときに調べるんで良さそう。

## Ch. 12 Iterators and Generators

  • ES6 で Iterator が導入された
  • Iterable なオブジェクトは、 for/of ループで回したり、 spread operator で展開したり、 destructuring assignment で利用したりできる

Iterator がどうやって動くか

  1. Iterable なオブジェクトとは、特別な iterator メソッド( Symbol.iterator )を持っているオブジェクトのことで、そのメソッドは iterator オブジェクトを返す。
  2. Iterator とは next() メソッドを持っているオブジェクトのことで、そのメソッドは iteration result オブジェクトを返す。
  3. Iteration result オブジェクトは valuedone というプロパティを持ったオブジェクトのこと。

ループするときは iteration result オブジェクトの donetrue になるまで処理を続ける。頑張って書くとこういう感じ

let iterable = [1, 2, 3];
let iterator = iterable[Symbol.iterator]();
for (let result = iterator.next(); !result.done; result = iterator.next()) {
    console.log(result.value);
}
  • ビルトインの iterable なオブジェクトの Iterator オブジェクトは、それ自体も Iterable になっていて自分自身を返す。
  • つまり Iterator オブジェクト自身も Symbol.iterator メソッドを持っているということ
  • これは途中まで使った iterator を iterate したい場合に便利
let iterable = [1, 2, 3, 4, 5];
let iterator = iterable[Symbol.iterator]();
iterator.next();
iterator.next();

console.log([...iterator]); // => [3,4,5]

Iterable なオブジェクトを実装する

まずはジェネレーター関数を使わずにやってみる。

class MyIterable {
    [Symbol.iterator]() {
        let next = 0;
        let max = 10;
        return {
            next() {
                return (next <= max) ? { value: next++ } : { done: true };
            }
        }
    }
}


for (let x of new MyIterable()) console.log(x);
// 0から10までが出力された

動いたー。わーい。

便利なので iterator 自身も iterable にしておく:

class MyIterable {
    [Symbol.iterator]() {
        let next = 0;
        let max = 10;
        return {
            next() {
                return (next <= max) ? { value: next++ } : { done: true };
            },

            // 便利なので iterator 自身も iterable にしておく
            [Symbol.iterator]() { return this; }
        }
    }
}

ループの途中の breakreturn や例外などでイテレーションが中断される場合がある。その際に、ファイルやメモリーの開放のような後処理ができるように iterator には return() メソッドを実装できる。return() メソッドが存在する場合は、中断時に引数なしで呼び出される。return() はオブジェクトを返さなければならないが、そのオブジェクトは無視される。

class MyIterable {
    [Symbol.iterator]() {
        let next = 0;
        let max = 10;
        return {
            next() {
                return (next <= max) ? { value: next++ } : { done: true };
            },
            // iterator を iterable にしておくと便利
            [Symbol.iterator]() { return this; },
            // 後処理用(実際はこのクラスでは不要だけど)
            return() {
                console.log("return() が呼び出された");
                return {};
            }
        }
    }
}

let iterable = new MyIterable();

console.log("途中終了しない場合1");
for (let x of iterable){
    //
}

console.log("途中終了しない場合2");
[...iterable];

console.log("途中終了する場合1 break");
for (let x of iterable){
    break;
}

console.log("途中終了する場合2 return");
(function(){
    for (let x of iterable){
        return;
    }
}());

console.log("途中終了する場合3 例外");
try{
    for (let x of iterable){
        throw new Error();
    }
} catch {}

console.log("途中終了する場合3 destructuring assignment");
let [v1, v2] = iterable;

と、こんな感じで Iterable なオブジェクトを実装することができるけど、ちょっと面倒くさい。そこでジェネレーターの登場。

Generators

  1. ジェネレーター関数は function*アスタリスクをつけて定義する。
  2. ジェネレーター関数を呼び出すと、実際にはその関数のボディは実行されずに、ジェネレーターオブジェクトが返される。
  3. このジェネレーターオブジェクトは iterable になっている
  4. next() を呼び出すとジェネレーター関数のボディが呼び出されて yield ステートメントのところまで実行される。
  5. yield は ES6 で導入されたのだけど、この yield ステートメントで指定された値が next() の戻り値になる。

ほえー。

function* myGenerator(){
    yield 1;
    yield 2;
    yield 3;
    yield 4;
}

let generator = myGenerator();

// ジェネレーターオブジェクトは Iterator オブジェクトになってるので next() を呼ぶことができる
console.log(generator.next().value); // 1
console.log(generator.next().value); // 2
console.log(generator.next().value); // 3
console.log(generator.next().value); // 4
console.log(generator.next().done); // true

// さらにジェネレーターはそれ自身が Iterable で自分自身を返す
console.log(generator === generator[Symbol.iterator]()); // true

// なのでジェネレーター自体を Iterable なオブジェクトとして扱うことができる
console.log([...myGenerator()]); // [ 1, 2, 3, 4 ]

落ち着いて考えないとこんがらがってくるー。

ジェネレーター関数の定義は関数宣言以外にもこんな感じでかける:

// 関数式
const myGenerator = function*(){
    yield 1;
    yield 2;
    yield 3;
    yield 4;
}

// メソッド
let obj = {
    *myGenerator(){
        yield 1;
        yield 2;
        yield 3;
        yield 4;
    }
}

ということで、さっきジェネレーターを使わずに頑張って書いてたやつは、こういう風に書ける

class MyIterable {
    *[Symbol.iterator]() {
        for (let v = 0; v <= 10; v++) {
            yield v;
        }
    }
}

おー( return() はどうなるんだろう?と思ったら後で出てきた↓)

yield*

ジェネレーターを使って Iterable をこんな風に扱いたい場合に:

function* sequence(...iterables) {
    for(let iterable of iterables) {
        for(let item of iterable) {
            yield item;
        }
    }
}

console.log([...sequence("abc",[1,2,3])]);
// [ 'a', 'b', 'c', 1, 2, 3 ]

yield* を使うとこう書ける:

function* sequence(...iterables) {
    for(let iterable of iterables) {
        yield* iterable;
    }
}
  • yield* は Iterable オブジェクトを指定すると、それぞれの結果を対象に yield できる

Advanced Generator Features

ジェネレーター関数は、イテレーターを作るときに使われるのがほとんどだけど、処理を途中で止めてあとで再開できるというのがそもそもの機能。なので、その辺をもうちょっと詳しく見ていく。

return

  • return を書くと、そこで処理が終わるので、途中で処理を終わらせたい場合に使用する。
  • return に値を指定すると donetrue なのに value にも値が指定されている状態になる。この値は for/of や spread operator では無視される。
function* myGenerator(){
    yield 1;
    yield 2;
    return 3;
}

let g = myGenerator();
console.log(g.next());
// => { value: 1, done: false }
console.log(g.next());
// => { value: 2, done: false }
console.log(g.next());
// => { value: 3, done: true }
console.log(g.next());
// => { value: undefined, done: true }

// return の value は無視される
console.log([...myGenerator()]);
// => [ 1, 2 ]

yield

  • yieldステートメントのように扱ってきたけど、実は式。値を持つことができる。
  • next() の引数に値を指定すると、それが yield 式の値になる。yield から再開するときにその値が取得できる。
function* myGenerator(){
    console.log("myGenerator start");

    let y1 = yield 1;
    console.log(`myGenerator y1=${ y1 }`);

    let y2 = yield 2;
    console.log(`myGenerator y2=${ y2 }`);

    let y3 = yield 3;
    console.log(`myGenerator y3=${ y3 }`);
}

let g = myGenerator();
console.log("main start");

let n1 = g.next("a");
console.log(`main n1.value=${ n1.value }`);

let n2 = g.next("b");
console.log(`main n2.value=${ n2.value }`);

let n3 = g.next("c");
console.log(`main n3.value=${ n3.value }`);

let n4 = g.next("d");
console.log(`main n4.value=${ n4.value }`);

結果はこうなった

main start
myGenerator start
main n1.value=1
myGenerator y1=b
main n2.value=2
myGenerator y2=c
main n3.value=3
myGenerator y3=d
main n4.value=undefined

最初に next() で使った "a" は受け取ることができないね。

return()

あ、さっき気になったやつだ。

  • ジェネレーターには return() メソッドが定義されている
  • ジェネレーターに対して独自の return() を実装することはできない。クリーンアップをしたい場合は try/finally を使う。
  • return() を呼び出すと、ジェネレーターはクリーンアップのコードを実行する

そっか finally を使えばいいのか。

function* myGenerator() {
    try {
        console.log("1の前");
        yield 1;
        console.log("2の前");
        yield 2;
        console.log("3の前");
        yield 3;
        console.log("3のあと");
    } finally {
        console.log("finallyが呼ばれたー");
    }
}

console.log("return() を直接呼び出し =======");
let g = myGenerator();
g.next();
g.next();
g.return();

console.log("destructuring assignment =======");
let [v1] = myGenerator();

console.log("for/ofをbreak =======");
for (let v of myGenerator()){
    break;
}

結果はこう:

return() を直接呼び出し =======
1の前
2の前
finallyが呼ばれたー
destructuring assignment =======
1の前
finallyが呼ばれたー
for/ofをbreak =======
1の前
finallyが呼ばれたー

throw()

  • throw() を呼び出すと、今ポーズしている yield のところで throw される
  • throw() の引数に値を渡すとジェネレーター内の try/catch でキャッチ可能

今日も面白かった。

JavaScriptの勉強中:その6 Classes (後編) / Modules

ぼーっと今日も。半分くらいきたかな。

bufferings.hatenablog.com

ちょこちょこ勉強しながら「分かるようになってるのかなぁ?」とか思ってたけど、今日たまたま機会があって読んだら JavaScript のコードだいぶ理解できるようになってた。よかった。

### 9.4 既存のクラスへのメソッド追加

  • JavaScript のプロトタイプベースの継承の仕組みはダイナミック。なので、オブジェクトが生成された後にプロトタイプのプロパティを変更しても、その内容は反映される。
  • なので後からでもプロトタイプにメソッドを追加したりできる。ビルトインのクラス(Object とか String とか)の振る舞いも変更できてしまう。
  • けど、そういうことは基本的には、やらんほうが良い(わかる

### 9.5 サブクラス

サブクラスも、まずは ES6 より前のやり方から。

サブクラスとプロトタイプ

Range のサブクラスとして Span を作りたいときは、ES6 より前だとこんな感じになる:

// Range のプロトタイプから Span のプロトタイプを作る
Span.prototype = Object.create(Range.prototype);

// Range のコンストラクターじゃなくて Span を使いたいので書き換える
Span.prototype.constructor = Span;

でもスーパークラスコンストラクターやメソッドをサブクラスから呼び出すためのシンプルな方法はなかった。

extendssuper

ということで ES6。

extends

  • ES6 以降だと単に classextends を使ってスーパークラスを指定できる
  • extends を使った場合は static メソッドも継承される。これは extends の特別な機能として導入された。

new.target

super

## Ch. 10 Modules

モジュール化に関しては以前は JavaScript のビルトインサポートがなかったので

  • みんな自分で頑張ってやってて
  • Nodeが require() 関数を使ったモジュール化を使用してたんだけど
  • ES6 でそれとは違う exportimport が導入された

という流れ。

### 10.1 Module with Classes, Objects and Closures

  • クラスやオブジェクトを使えば、同じ名前のメソッドやプロパティがあってもぶつからないので、モジュールのように扱うことができる。
  • けど、内部実装の詳細をモジュールの中に隠す方法がない
  • なので、即時実行関数を使ったら内部実装の詳細をその関数に閉じ込めることができる
// aaa() や bbb() や CCC は外からは見えなくて
// Abcde だけが見える
const Abcde = (function () {
    function aaa(){
        // ...
    }
    function bbb(){
        // ...
    }
    const CCC = 1;

    return class Abcde {
        // ...
    }
}());
// 複数公開したいとき
const stats = (function () {
    function aaa(){
        // ...
    }
    function bbb(){
        // ...
    }
    const CCC = 1;

    function mean(data) {
        // ...
    }

    function stddev(data) {
        // ...
    }

    return { mean, stddev };
}());

stats.mean([1,2,3,4,5]);
stats.stddev([1,2,3,4,5]);

Closure-Based Modularity

  • そう考えると、単に JavaScript ファイルに書いてあるコードの前後に決まったテキストを入れて機械的に処理することで、モジュール化できることに気づく。
  • 例えば、ファイルの一覧を受け取ってそのコンテンツを即時実行関数で囲んで次のようにして一つの巨大なファイルを作ると:
const modules = {};
function require(moduleName) { return modules[moduleName]; }

modules["abcde.js"] = (function () {
    const exports = {};

    // abcde.js の中身
    function aaa() {
        // ...
    }

    exports.Abcde = class Abcde {
        // ...
    };

    return exports;
}());

modules["stats.js"] = (function () {
    const exports = {};

    // stats.js の中身
    exports.mean = function (data) {
        // ...
    }

    exports.stddev = function (data) {
        // ...
    }

    return exports;
}());
  • こんな風にして利用できる:
const Abcde = require("abcde.js");
const stats = require("stats.js");

let abcde = new Abcde();
let average = stats.mean([1,2,3,4,5]);
  • というのが、コードバンドリングツールのラフスケッチであり、Nodeなどで使われてる require() 関数の簡単な紹介でもある。

へー。

### 10.2 Modules in Node

Node では

  • require() 関数で import する
  • exports オブジェクトのプロパティに設定するか module.exports オブジェクト自体をリプレースするかで export する
  • 明示的に export したもの以外はそのファイル内でプライベートになる。

Node Exports

  • Node は exports というグローバル変数を定義している
  • export したかったら exports のプロパティとしてアサインすればいい:
const aaa = ...
const bbb = ...

exports.mean = data => ...
exports.stddev = function(d) {...}
  • でもだいたいの場合は export したいのは1個だけなのでそういうときは単に module.exports を使えば良い
module.exports = class Abcde { ... }
  • module.exports のデフォルト値は exports なので module.exports.mean = mean みたいに書くこともできるし、次のように書くこともできる:
module.exports = { mean, stddev }

ちょっと exportsmodule.exports の違いがよくわかんなくて公式サイトの説明を見てみた:

Modules | Node.js v14.3.0 Documentation

なるほど。

  • exportsmodule.exports のショートカットで、わざわざ module. をつけなくても良いように用意してくれてる
  • けど、 exports 自体を別のオブジェクトに書き換えてしまうと module.exportsexports が別のオブジェクトになってしまうから export されない
  • だからオブジェクト全体を書き換えなくていい場合には短く書ける exports を使えばいいし、全体を書き換えたい場合は module.exports を使えば良い

ってことか。へー。

Node Imports

  • import には require() 関数を使う
  • Node のシステムモジュールを import する場合はそのモジュールの名前を書くだけ:
const fs = require("fs");
const http = require("http");
  • 自分のモジュールを import したい場合はそのファイルからの相対パスで書く:
const stats = require('./stats.js');

(ダブルクォーテーションだったりシングルクォーテーションだったりするのは良くわからない。関係ないと思う。)

  • 絶対パスも書けるけど普通は使わない(そうだろうな
  • 拡張子( .js )を書かなくても大丈夫だけどつけるほうが一般的
  • 対象モジュールが複数のプロパティを export している場合は destructuring assignment で利用するものだけ受け取ることができる

んで ES6 で公式の JavaScript モジュールが出てきた。Node 13 では ES6 のモジュールがサポートされてるらしいけど、今のところは多くの Node のプログラムはまだ Node のモジュールを使ってる。(ふむー大変そう

### 10.3 Moudles in ES6

  • コンセプトとしては Node と同じ:export されていない限り各ファイルの中でプライベートになる
  • 違うのは構文。それと、Webブラウザーでモジュールを定義する方法も違う。

ES6 のモジュールは普通の JavaScript の "script" とはいくつかの点で異なる

  • 通常のスクリプトだとトップレベル宣言はグローバルになるけど、モジュールの場合はそれぞれのファイルがプライベートなコンテキストを持っている。
  • モジュールは自動的に strict mode になる。 'use strict' を書く必要はない。

ES6 Exports

  • 単に宣言の前に export をつければいい
export const PI = Math.PI;
export function abcde() {...}
export class Circle {...}
  • 一箇所にまとめたい場合は、それぞれの場所に export をつけるんじゃなくて、最後にひとまとめにして export すればいい
export { Circle, abcde, PI };
  • これオブジェクトのように見えるけどそうじゃなくて、 export の構文として波括弧の中でカンマ区切りのリストを受け取るようになってる
  • 1個だけ export したい場合、通常は export default を使う
export default class Abcde {
    // ...
}
  • export default を使うと import が少しだけ楽になる
  • 通常の export は名前がついてるものだけしかできないが eport default は匿名関数や匿名クラスを含むどんな式でも export できる
  • つまり export defeault はオブジェクトリテラルを export できるので、export default の次に波括弧で囲んであるものがあったら、さっきのと違ってそれは本当にオブジェクト。(へーww
  • exportexport default の両方を書くことは可能だけど普通はやらない。export default を書くならそれいっこだけ。
  • export はトップレベルにしかかけない。関数やクラスやループや分岐の中では使えない。つまり実行前に静的に決まるということ。

ES6 Imports

  • export default で export されてる場合はこんな風に書ける:
import Abcde from './abcde.js';
  • 識別子は定数になる
  • import は hoist されるけど普通は一番上に書く
  • export で export されてる場合は destructuring assignment のように書いて受け取る(「ように」ってことは実際はそうじゃないんだろうな)
import { mean, stddev } from "./stats.js";
  • スタイルガイドではちゃんと全部のシンボルを書くことが推奨されてるけど、全部まとめて import することもできる。 export が多い場合とか:
import * as stats from "./stats.js";
  • この場合 stats.mean()stats.stddev() のように stats オブジェクトを介して呼び出すことができる
  • あんまり一般的ではないけど、もし exportexport default が混ざってたらこんな風に書くことができる:
import Histogram, { mean, stddev } from "./histogram-stats.js";
  • もうひとつ別の使われ方としてこういう書き方もできる:
import "./analytics.js";
  • これはそのモジュールの関数を使う必要がない場合に使う。そのモジュールを呼び出すだけで処理をしてくれてそれでOKな場合など。
  • import するときに別名をつけることができる。名前がかぶる場合など:
import { render as renderImage } from "./imageutils.js";
import { render as renderUI } from "./ui.js";
  • export default の場合はそもそも名前を持ってないからいつでも名前をつけてるけど、さっきの例みたいに exportexport default が混ざっている場合にはこういうこともできる:
import { default as Histogram, mean, stddev } from "./histogram-stats.js";

Re-Exports

import したものを export したい場合には、こういう風にかけるが:

import { mean } from "./stats/mean.js";
import { stddev } from "./stats/stddev.js";
export { mean, stddev };

ES6 ではそれ用の特別な書き方を用意してくれてる:

export { mean } from "./stats/mean.js";
export { stddev } from "./stats/stddev.js";

ワイルドカードもいける:

export * from "./stats/mean.js";
export * from "./stats/stddev.js";

リネームもOK:

export { mean as average } from "./stats/mean.js";
export { stddev } from "./stats/stddev.js";

もし mean.js と stddev.js が export default を使っていた場合はこうなる:

export { default as mean } from "./stats/mean.js";
export { default as stddev } from "./stats/stddev.js";

逆に default として export したい場合には:

export { mean as default } from "./stats/mean.js";

Dynamic Imports with import()

  • ES2020 で導入。モジュールの動的インポート。Promise を返す。
  • クライアントWebアプリでは全部静的にインポートするより動的にインポートしたいからDOMを操作して実現してたけど、それが標準化された。
import("./stats.js").then(stats => {
    let average = stats.mean(data);
})

もしくは async 関数で使う

async analyzeData(data) {
    let stats = await import("./stats.js");
    return {
        average: stats.mean(data);
        stddev: stats.stddev(data);
    };
}
  • import の場合は文字列リテラルじゃないといけなかったけど、 import() の場合は文字列として評価される式ならなんでも大丈夫
  • import() は関数のように見えるけどそうじゃなくて operator 。
  • ブラウザだけじゃなくて、 webpack みたいなコードパッキングツールでも使える。static な import を使うと全部バンドルして大きなファイルになってしまうけど、 dynamic な import をうまく使えば、小さなバンドルに分けることができる。

なるほどねぇ。

JavaScriptの勉強中:その5 Functions / Classes (途中まで)

今日も読むー。

bufferings.hatenablog.com

## Ch. 8 Functions

### 8.1 Defining Functions

関数宣言

function distance(x1, y1, x2, y2) {
  let dx = x2 - x1;
  let dy = y2 - y1;
  return Math.sqrt(dx*dx + dy*dy);
}
  • 関数につけた名前が変数名になる
  • 関数宣言ステートメントスクリプトや関数またはブロックの一番上に巻き上げられる(hoisted)。なので、定義してある場所より前でも呼び出すことができる。
  • ES6 より前は JavaScript ファイルまたは関数内のトップレベルでの定義しか許可されていなかったが、ES6 の strict mode ではブロック内での宣言が可能となった。そのブロック内からのみ利用可能となる。

関数式

const square = function(x) { return x*x; };
  • 関数名はオプショナル。関数宣言と違って、名前をつけても変数は登録されない。
  • 名前をつけると関数内のローカル変数になるので、再帰呼び出しで利用できる。
  • 関数式は hoist されないので定義前には利用できない。

アロー関数

  • ES6
// 全部書くとこう
const basic = (x, y) => { return x + y; };

// return ステートメントだけの場合は return と {} を省略できる
const simple = (x, y) => x + y;

// 引数が1つだけの場合は () を省略できる
const oneParam = x => x*x;

// 引数がない場合は () が必要
const noParam = () => 10;

// オブジェクトリテラルを返したい場合は曖昧になるので普通に書くか
const f = x => { return { value: x }; };
// () で囲む
const g = x => ({ value: x });
  • アローの前に改行を入れたらそこで終わってるって解釈されるからダメ
  • アロー関数は this を持たないので関数が定義されたコンテキストの this を参照する

this の扱いが一番大切そうだな。

### 8.2 Invoking Functions

  • 関数は「関数」または「メソッド」として呼び出される
  • return がなければ undefined が返される
  • 関数として呼び出された場合
    • this は、non-strict mode なら global object、strict mode なら undefined になる。
    • アロー関数は別。定義された場所の this を使う。
    • 基本的には関数では this は使わない。
  • メソッドは
    • オブジェクトのプロパティとして保持されている関数
    • そのオブジェクトを this で参照できる
    • メソッド内で定義した関数の中で this を使ってもメソッドのオブジェクトをは参照できないので注意。その場合は関数呼び出しとなるため。
"use strict";

let obj = {
    m: function() {
        console.log("mの中のthis=" + (this === obj));
        f();
        function f() {
            console.log("fの中のthis=" + this);
        }
    }
}
obj.m();

mの中のthis=true
fの中のthis=undefined

f() の中で this を使いたい場合は

  • アロー関数を使う
  • 一度ローカル変数に入れてそれを参照する
  • thisbind する

そういうときはアロー関数が一番読みやすそうね。

### 8.3 Function Arguments and Parameters

デフォルト値

  • デフォルト値の指定には以前は a = a || [] のように || オペレーターを使ってた
  • ES6 以降だとパラメーターにデフォルト値を指定できるのでそっちを使えばいい
    • デフォルト式には定数やリテラル以外にも変数や関数呼び出しも使える。その前のパラメーターも使える。へー。

Rest Parameters

  • ES6
  • Rest Parameters を使うと、数の決まっていないパラメーターを配列で受け取ることができる
function sample(first, ...rest) {
}
  • 3つのピリオド
  • 最後のパラメーターのみ
  • 空の配列にはなりうるが、 undefined になることはない

arguments

  • ES6 より前は arguments オブジェクトを使っていた
    • arguments オブジェクトは array-like オブジェクト
    • 引数名ではなくインデックスで参照

古いコードを読むときに出てくるかも、くらいだな。

Spread Operator

  • 関数呼び出し時に spread operator ... を使用することも可能
let numbers = [5, 2, 10];
Math.min(...numbers);

Destructuring assignment

  • パラメーターに destructuring assignment を使うことができる
// 配列
function vectorAdd([x1, y1], [x2, y2]) {
    return [x1 + x2, y1 + y2];
}
vectorAdd([1,2], [3,4]);

// オブジェクト
function vectorMultiply({x, y}, scalar) {
    return { x: x*scalar, y: y*scalar };
}
vectorMultiply({x: 1, y: 2}, 2);

// 違う変数名で受け取る
function vectorAdd2({x: x1, y: y1}, {x: x2, y: y2}) {
    return { x: x1 + x2, y: y1 + y2 };
}
vectorAdd2({x: 1, y: 2}, {x: 3, y: 4});

// デフォルト値を設定することもできる
function vectorMultiply2({x, y, z = 0}, scalar) {
    return { x: x*scalar, y: y*scalar, z: z*scalar };
}
vectorMultiply2({x: 1, y: 2}, 2);

// 配列の残りを rest parameter で受け取ることもできる
// 本当の rest parameter とは別物
function f([x, y, ...coords], ...rest) {
    return [x+y, ...rest, ...coords];
}
f([1,2,3,4],5,6); // => [ 3, 5, 6, 3, 4 ]

// (ES2018) オブジェクトに対しても rest parameter が使える
function vectorMultiply3({x, y, z=0, ...props}, scalar) {
    return { x: x*scalar, y: y*scalar, z: z*scalar, ...props };
}
vectorMultiply3({x: 1, y: 2, w: -1}, 2); // => { x: 2, y: 4, z: 0, w: -1 }

// オブジェクトと配列の混ざったやつもOK
function drawCircle({x, y, radius, color: [r, g, b]}) {
}
  • object の destructured parameters を使うと、名前付き引数に似たものを実現できる

### 8.4 Functions as Values

  • function はオブジェクトの一種なのでプロパティを持つことができる
f.counter = 0;
function f() {
    return f.counter++;
}
f(); // => 0
f(); // => 1

んー。面白いけど、Closureでやれば良さそう。

### 8.5 Functions as Namespaces

これ、jQueryでよく見たやつかな。

  • 関数内で定義されたローカル変数は外からは参照できないので汚さなくて済む
function chunkNamespace() {
    // ここに色々処理
}
chunkNamespace(); // 呼び出しを忘れずに

でもこれだと chunkNamespace という関数自体は登録されてしまうので、それもなくすとこうなる:

(function() { // 最初の丸括弧でこれが関数宣言じゃなくて関数式だとみなされる
    // ここに色々処理
}()); // 呼び出し

### 8.6 Closure

  • JavaScript はレキシカルスコープなので、関数が定義された場所のローカル変数がバインドされる。そういうのを Closure と呼ぶ。

あぁ、さっきのやつの Closure 版でてきた:

let f = (function() {
    let counter = 0;
    return function() { return counter++; };
}());
f(); // => 0
f(); // => 1
  • this を使いたいときはローカル変数にしてそれを参照することで、使うことができる。そういうときはだいたい self とか that って名前を使うのかな?
const self = this;

### 8.7 Function Properties, Methods, and Constructor

call() apply()

関数をあるオブジェクトに対するメソッドのように呼び出すことができる。第一引数に渡したオブジェクトが this になる。

f.call(obj);
f.apply(obj);
  • アロー関数の this はレキシカルスコープを使うから call()apply() では上書きされない。
  • call() は関数に渡したい引数をそのまま第二引数以降に指定する
  • apply() は関数に渡したい引数を第二引数に配列として指定する
f.call(obj, 1, 2);
f.apply(obj, [1, 2]);
  • ES5 までは spread operator がなかったから、任意の数の引数をとる関数に対しては apply() で配列を渡してた。
  • ES6 以降だと spread operator があるから call() だけでも良さそうかな。

bind()

  • 関数にオブジェクトをバインドして新しい関数を返す
  • this はそのオブジェクトになる
  • バインド済みの関数をオブジェクトのプロパティに設定しても、既にバインドしてるオブジェクトに対して呼び出される
  • アロー関数の this は上書きされない
  • bind() のよくある使い方は、アロー関数じゃない関数をアロー関数のようにしてしまうこと

でも bind() メソッドには単にバインドする以上のことができる

  • bind() の2番目以降の引数に値を渡すと、その引数もバインドされる(currying)
> let sum = (x,y) => x + y;
undefined
> let succ = sum.bind(null, 1);
undefined
> succ(2);
3

### 8.8 Functional Programming

読んで「面白いなー」と思ったけど、今はあんまり覚えなくていいかなぁと思ったので頭の片隅に入れとくくらいにしとく。

## Ch. 9 Classes

ES6 で class キーワードが導入されたりして書きやすくなったけど、まずは古いスタイルからたどっていく。

### 9.1 プロトタイプ

  • JavaScript でいう「クラス」は、同じプロトタイプオブジェクトからプロパティを継承しているオブジェクトのこと
  • Object.create() でプロトタイプオブジェクトを指定すれば JavaScript のクラスができる

こんな感じ

function range(from, to) {
    let r = Object.create(range.methods);
    r.from = from;
    r.to = to;
    return r;
}

// 関数オブジェクトのプロパティとして定義してる
range.methods = {
    includes(x) { return this.from <= x && x <= this.to; },
}

let r = range(1,3);
console.log(r.includes(2)); // => true
  • range() がファクトリー関数になってる
  • range.methods で定義してるメソッドの中で this を使って呼び出し元のオブジェクトにアクセスしてる

### 9.2 コンストラクター

// Range オブジェクトのコンストラクター関数
// コンストラクター関数は最初を大文字で始めるのが慣習
function Range(from, to) {
    // new で呼び出されると自動的に作成されたオブジェクトが this に入ってて
    // それが戻り値として返される
    this.from = from;
    this.to = to;
}

// コンストラクター関数の prototype プロパティがプロトタイプとして使用される
Range.prototype = {
    includes(x) { return this.from <= x && x <= this.to; },
}

let r = new Range(1,3);
console.log(r.includes(2)); // => true

instanceof

  • 2つのオブジェクトのプロトタイプオブジェクトが同じだったら、それは同じクラスのインスタンスだということ
  • コンストラクター関数が違っていてもプロトタイプが同じなら同じクラスだと言える
  • instanceofコンストラクター関数の prototype プロパティをオブジェクトが継承している場合に true を返す
r instanceof Range
range.methods.isPrototypeOf(r);

constructor プロパティ

  • さっきは Range.prototype を定義したけど、JavaScriptの関数は最初から prototype プロパティを持ってる
  • で、その事前に定義された prototype プロパティには constructor というプロパティがあって、それが元の関数を指している
  • なので、普通の関数を new を使って呼び出したら constructor というプロパティが存在して、それがコンストラクターとして使用した関数を指すことになる
> let F = function(){};
undefined
> let obj = new F();
undefined
> obj.constructor === F;
true

おー

  • さっき定義した Range 関数は prototype プロパティを上書きしてるから、ちゃんと constructor プロパティを定義しておく方がいいい
Range.prototype = {
    constructor: Range,

    includes(x) { return this.from <= x && x <= this.to; },
}
  • もうひとつ、古いコードで見られる方法は、事前に定義された prototype にメソッドを追加する方法
Range.prototype.includes = function(x) {
    return this.from <= x && x <= this.to;
}
Range.prototype.otherMethod = function() {
    // ...
}

### 9.3 class キーワード

ついにきた。ES6 で導入された class キーワード。

class Range {
    constructor(from, to) {
        this.from = from;
        this.to = to;
    }

    includes(x) { return this.from <= x && x <= this.to; }
}

let r = new Range(1,3);
console.log(r.includes(2)); // => true

これでさっきのやつと全く同じになる。

  • コンストラクター関数は constructor というキーワードで定義するけど Range という変数が使用されて、 Range というコンストラクター関数になる。
  • また constructor プロパティに Range 関数が入ってる
  • メソッドの定義が複数ある場合でもカンマは不要

大切なこと2つ

  • class 定義の中は暗黙的に strict mode になる
  • class 定義は関数定義と違って hoist されない

Static Methods

  • static キーワードをつけて static メソッドを定義できる
  • static メソッドは、プロトタイプオブジェクトのプロパティではなく、コンストラクターのプロパティとして定義される

Getter, Setter

  • オブジェクトのときと同じようにして Getter と Setter を定義できる

フィールド

  • ES6 ではメソッドの定義しか許可されていない
  • フィールドを定義したい場合は、コンストラクター関数かメソッドの中で生成しないといけない
  • static フィールドを定義したい場合は、クラスを定義した後に外側で定義しないといけない
class Range {
    constructor(from, to) {
        // フィールドを定義
        this.from = from;
        this.to = to;
    }

    includes(x) { return this.from <= x && x <= this.to; }
}

// static フィールドを定義
Range.MIN = -100;

新たに標準化が進んでいる書き方

現在、フィールドに対する標準化が進んでるところ。こんな風にかけるようになる。

class Range {
    from = 0;
    to = 0;

    includes(x) { return this.from <= x && x <= this.to; }
}
  • フィールドの初期化をコンストラクターの外に書くことができる
  • フィールドの初期値を書かなくても大丈夫。でも、その場合は undefined になるからちゃんと初期値を設定したほうがいい。

プライベートフィールドも定義できるようになる。 #プレフィックス。クラス内では使えるけど、外からは参照できない。

class Buffer {
    #size = 0;
    get size() { return this.#size; }
}

static フィールドはフィールド宣言の前に static キーワードをつければ良い。

眠くなってきたから今日はこれくらいにしとこ。次は 9.4 から。

JavaScriptの勉強中:その4 Arrays

よしやるかー。先に言っておくと Arrays だけで力尽きた。

bufferings.hatenablog.com

## Ch. 7 Arrays

  • 32-bit index なので index の範囲は 0 から 4,294,967,294 まで
> let a = []
undefined
> a[4294967294] = 1
1
> a.length
4294967295
> a[4294967295] = 2
2
> a.length
4294967295
> a
[ <4294967294 empty items>, 1, '4294967295': 2 ]

へー。4294967295はキーになっちゃったし length も変わらないのか。面白いな。

### 7.1 Creating Arrays

Array Literal []

  • カンマだけ書いて値を書かなかったら間の要素が抜けた sparse array になる。その要素を参照しようとすると undefined になる
> let count = [1,,3]
undefined
> count
[ 1, <1 empty item>, 3 ]
> count[1]
undefined
  • ちなみに Array literal は末尾のカンマを許可してるので [,,] の length は3じゃなくて2
  • ES6 以降では spread operator が利用できる
> let a = [1,2,3];
undefined
> [0, ...a, 4]
[ 0, 1, 2, 3, 4 ]
  • spread operator は iterable なオブジェクトに対して利用できる。ので、文字列を渡すとこうなる:
> [..."abcde"]
[ 'a', 'b', 'c', 'd', 'e' ]

new Array(), Array.of(), Array.from()

  • 配列の生成には Array() コンストラクターも使用できる。けど、引数の数で動きが変わるのが危なそうなので、使わないだろうな。
  • new Array() はそんな感じなので ES6 で Array.of() が導入された。けどリテラルで良さそう。
  • ES6 で Array.from() も導入された。これは iterable や array-like なオブジェクトから配列を生成できる。
    • iterable オブジェクトの場合は、これと同じ [...iterable]
    • array-like オブジェクトの場合は array-like から本当の array を作成するのに便利(array-like オブジェクトは、Array じゃないけど数値の length プロパティがあって、その長さ分の数値のプロパティを持ってるオブジェクトのこと)
    • オプショナルで2つ目の引数として関数を渡すことができて要素に変換をかけることができる。 map みたいな感じ。

### 7.2 Reading and Writing Array Elements

  • 232-1 より小さい非負の整数のプロパティ名を指定したら、自動的に length プロパティが更新される。あー、これが今日のはじめで length が更新されなかったやつか。

### 7.4 Array Length

  • 現在の length の値以上の index に値を設定した場合には length がその index + 1 の値に更新される
  • length に現在の値よりも小さな値(非負)を設定したら、その値以上の要素は削除される

### 7.6 Iterating Arrays

  • ES6以降では for/of ループで処理を行うことができる
> for(const v of [..."Hello World"]) {
... console.log(v);
... }
H
e
l
l
o
 
W
o
r
l
d
  • index を使用したい場合には entries() を使う
> for(const [i, v] of [..."Hello World"].entries()) {
... console.log(`${i}:${v}`);
... }
0:H
1:e
2:l
3:l
4:o
5: 
6:W
7:o
8:r
9:l
10:d
  • forEach() もある
> let uppercase = "";
undefined
> [..."Hello World"].forEach(c => uppercase += c.toUpperCase())
undefined
> uppercase
'HELLO WORLD'
  • forEach() は第二引数で index を受け取ることもできる
> [..."Hello World"].forEach((v,i) => console.log(`${i}:${v}`))
0:H
1:e
2:l
3:l
4:o
5: 
6:W
7:o
8:r
9:l
10:d
  • forEach()for/of ループと違って sparse array の存在しない要素に対しては呼び出されない
// for/of
> for(const v of [1,,,4]){
... console.log(v);
... }
1
undefined
undefined
4

// forEach()
> [1,,,4].forEach(v => console.log(v));
1
4

ん? undefined を要素として入れたらどうなるんだろう?

> [1,2,undefined,4].forEach(v => console.log(v));
1
2
undefined
4

へー。その場合は処理されるのか。 sparse array の要素がないことの表現としての undefined と実際に要素として設定された undefined は区別されてるんだね。

  • あとは普通の for loop ももちろん使用可能

### 7.8 Array Methods

forEach()

  • 途中でイテレーションを終了させる方法はない。つまり普通の for ループの break に相当する仕組みはない。
  • さっきも書いたけど sparse array の存在しない要素に対しては呼び出されない

map()

  • sparse array の場合は存在しない要素に対しては呼び出されないが、返される配列も sparse になる。
> [1,,,4].map(v => v*2);
[ 2, <2 empty items>, 8 ]

filter()

  • sparse array の存在しない要素はスキップされる
> [1,,,4].filter(v => v > 1)
[ 4 ]
  • なので、 sparse array の隙間をなくしたかったらこんな感じにかける:
> [1,,,4].filter(() => true)
[ 1, 4 ]

find() findIndex()

  • 最初にみつかった要素、またはインデックスを返す
  • 見つからなかった場合、 find()undefinedfindIndex()-1 を返す

every() some()

  • every() は全ての要素が条件を満たした場合に true を、それ以外の場合は false を返す。ひとつでも条件を満たさないものがあったら false を返すので、空の配列だと true になる。
  • some() は少なくとも1つの要素が条件を満たした場合に true を、それ以外の場合は false を返す。ひとつでも条件を満たすものがあったら true を返すので、空の配列だとfalse になる。
> [].every(v => v > 0 )
true
> [].some(v => v > 0 )
false

reduce() reduceRight()

  • reduce() は左から、reduceRight() は右から要素を処理して reduce する
> [1,2,3,4,5].reduce((acc,cur) => acc+cur, 0)
15

初期値を省略したら最初の要素が初期値になる。

> [1,2,3,4,5].reduce((acc,cur) => acc+cur)
15

この場合、最初の呼び出しで1番目と2番目の要素が渡されるので、1回処理が減る。

// 初期値を指定する場合
> [1,2,3,4,5].reduce((acc,cur) => {console.log(`(${acc}, ${cur})`); return acc+cur;}, 0)
(0, 1)
(1, 2)
(3, 3)
(6, 4)
(10, 5)
15

// 初期値を指定しない場合
> [1,2,3,4,5].reduce((acc,cur) => {console.log(`(${acc}, ${cur})`); return acc+cur;})
(1, 2)
(3, 3)
(6, 4)
(10, 5)
15
  • reduce で表現された処理はすぐ複雑になって理解するのが難しくなるから、普通のループを使う方が読み書きしやすいかも

flat() flatMap()

  • ES2019 で導入された
  • flat() は配列をフラットにできる。引数なしだと1段階。引数でフラット化のレベルを指定できる
  • flatMap()map の結果をフラット化できる

concat()

  • 配列をつなげることができる
  • 配列の要素は展開される(再帰的には展開されない)
> let a = [1,2,3]
undefined

// 複数要素を追加
> a.concat(4,5)
[ 1, 2, 3, 4, 5 ]

// 配列は展開される
> a.concat([4,5],[6,7])
[
  1, 2, 3, 4,
  5, 6, 7
]

// 配列の配列は展開されない
> a.concat(4,[5,[6,7]])
[ 1, 2, 3, 4, 5, [ 6, 7 ] ]

// 元の配列は変更されない
> a
[ 1, 2, 3 ]

spread operator で良さそうかな。

push() pop() shift() unshift()

  • push() pop(): array を stack のように扱うことができる
  • push() には複数の要素を渡すことができるけど concat() と違って配列は展開されない
  • unshift() shift(): array の末尾ではなく先頭に対して処理を行う
  • push()shift() を使うと queue のように扱うことができる
  • unshift() に複数の要素を渡すと前から順番に追加されるのではなくて、一気に追加されることに注意
// 順番に追加した場合
> let a = []
undefined
> a.unshift(1)
1
> a.unshift(2)
2
> a
[ 2, 1 ]

// 一気に追加
> a = []
[]
> a.unshift(1,2)
2
> a
[ 1, 2 ]

slice()

  • 指定された配列のスライスを返す。
  • 最初の引数が開始インデックスで、2番目の引数が終了インデックス。終了インデックスは含まれない。
  • 開始インデックスだけを渡すと、そこから最後まで
  • 負のインデックスを渡すと length からの相対的な位置になる。つまり -1 は最後の要素。
  • 元の配列は変更されない

splice()

  • 配列に要素を追加したり、配列から要素を削除したりする。 slice() と名前が似てるけど全然違う動きをするから注意。
  • slice()concat() と違って元の配列を変更する。
  • 個人的には引数が分かりにくいし、あんまり使いたくないかなぁ。使うときは調べてから使おっと。

fill()

  • 配列を指定した値で埋める
  • 元の配列を変更する
  • オプショナルな2番目と3番目の引数はそれぞれ開始インデックスと終了インデックス(含まない)。

copyWithin()

  • 配列のスライスをその配列の新しい場所にコピーする
  • 元の配列を変更する
  • これも頭の片隅に覚えておくぐらいにしといて、使うときに調べよっと

indexOf() lastIndexOf()

  • それぞれ前からと後ろからと探してインデックスを返す
  • 見つからなかったら -1 を返す
  • 比較には === を使用する
  • オブジェクトの場合は参照が一致するかを見るので、内容を見たい場合は find() を使って自分で作った predicate を渡せばいい
  • 開始インデックスを指定することもできる
  • 文字列にも indexOf()lastIndexOf() があって array のメソッドと似てるんだけど、開始インデックスに負の値を指定しても 0 として扱われる点に注意

includes()

  • ES2016
  • 配列が指定された値を含む場合は true を、そうでない場合は false を返す
  • 比較には SameValueZero を使用するため NaN をチェックすることができる

等価性の比較と同一性 - JavaScript | MDN

sort()

  • 元の配列を並び替える
  • 何も指定しなかったらアルファベット順に並び替える。 undefined は最後になる
  • 比較の関数を指定することができる

reverse()

  • 元の配列を逆順に書き換える

join() toString()

  • 文字列に変換するメソッド。ログやエラーメッセージ用に使うことができる
  • あとで使うためにシリアライズしたりしたい場合はこれらのメソッドではなく JSON.stringify() を使う
  • join(): 引数でセパレーターを指定して結合できる。引数なしだとカンマでつなぐ。
  • toString(): join() の引数なしバージョンと同じ

出力内容には[] とかはないことに注意

> [1,2,3].toString()
'1,2,3'
> ["a","b","c"].toString()
'a,b,c'
> [1,[2,"c"]].toString()
'1,2,c'

Array.isArray()

  • static method
  • 配列かどうかをチェックする

7.9 Array-Like Objects

  • length プロパティがあって、それに対応する非負の整数をプロパティとして持っているオブジェクト
  • 例えば document.querySelectorAll() は array-like オブジェクトを返す
  • for ループで配列のようにイテレートすることができるし、配列のメソッドを適用したりすることができる
> let a = {"0": "a", "1": "b", "2": "c", length: 3}
undefined
> Array.prototype.join.call(a)
'a,b,c'
> Array.prototype.map.call(a, x => x.toUpperCase())
[ 'A', 'B', 'C' ]
  • Array.from() で true array に変換できる

今日はここまでー。次は Functions だなー。先は長い。

Istio 1.6.0でIstio自身のCanary upgradeをお試し

お昼の続き。土曜日を満喫してる。お昼はIn place upgradeをしたけど、今回はCanary upgradeに挑戦。

bufferings.hatenablog.com

## 準備

とりあえずIstio 1.5.4を入れたGKEを準備した。

❯ bin/istioctl version                                                     
client version: 1.5.4
control plane version: 1.5.4
data plane version: 1.5.4 (7 proxies)

## インストールを実行

ここを読みながら:

https://istio.io/docs/setup/upgrade/

んー。ドキュメントにはこう書いてあるんだけど:

$ istioctl install --set revision=canary

なんとなく、設定ファイルを指定した方が良いのでは?と思った。指定しないとdefaultプロファイルが使われる気がする。なので、コマンドのリファレンスをチェックしてみたら:

https://istio.io/docs/reference/commands/istioctl/#istioctl-install

-f オプションがあるから大丈夫そうかな。こうやってみるかな?

❯ bin/istioctl install --set revision=canary -f config.yaml
✔ Istio core installed
✔ Istiod installed
✔ Ingress gateways installed
✔ Installation complete

podをwatchしといたらこうなった

❯ kubectl -n istio-system get pods -w
NAME                                    READY   STATUS    RESTARTS   AGE
istio-ingressgateway-7f76b9fc9f-ffr4j   1/1     Running   0          4m29s
istiod-6558b7b7f4-9d2dk                 1/1     Running   0          4m50s

# ここでinstallを実行した

istiod-canary-848948494f-vrpz8          0/1     Pending   0          0s
istiod-canary-848948494f-vrpz8          0/1     Pending   0          0s
istiod-canary-848948494f-vrpz8          0/1     ContainerCreating   0          0s
istiod-canary-848948494f-vrpz8          0/1     Running             0          9s
istiod-canary-848948494f-vrpz8          1/1     Running             0          10s
istio-ingressgateway-ccbb848f5-b94mh    0/1     Pending             0          0s
istio-ingressgateway-ccbb848f5-b94mh    0/1     Pending             0          0s
istio-ingressgateway-ccbb848f5-b94mh    0/1     ContainerCreating   0          1s
istio-ingressgateway-ccbb848f5-b94mh    0/1     Running             0          8s
istio-ingressgateway-ccbb848f5-b94mh    1/1     Running             0          10s
istio-ingressgateway-7f76b9fc9f-ffr4j   1/1     Terminating         0          5m37s
istio-ingressgateway-7f76b9fc9f-ffr4j   0/1     Terminating         0          5m43s
istio-ingressgateway-7f76b9fc9f-ffr4j   0/1     Terminating         0          5m44s
istio-ingressgateway-7f76b9fc9f-ffr4j   0/1     Terminating         0          5m44s

終わったあとはこうなった:

❯ kubectl get pods -n istio-system
NAME                                   READY   STATUS    RESTARTS   AGE
istio-ingressgateway-ccbb848f5-b94mh   1/1     Running   0          7m16s
istiod-6558b7b7f4-9d2dk                1/1     Running   0          13m
istiod-canary-848948494f-vrpz8         1/1     Running   0          7m29s

## つまり

  • istiod-canary が追加でデプロイされてる。元の istiod はそのまま。
  • istio-ingressgateway は、元のやつが停止されて、新しくデプロイされてる。

ふむ。そうなのか。istio-ingressgateway の中を見てみても1.6.0になってるな。

❯ kubectl -n istio-system get pods istio-ingressgateway-ccbb848f5-b94mh -o yaml | grep image: 
    image: docker.io/istio/proxyv2:1.6.0
    image: istio/proxyv2:1.6.0

バージョンはこんな感じ:

❯ bin/istioctl version
client version: 1.6.0
pilot version: 1.5.4
istiod version: 1.6.0
data plane version: 1.5.4 (6 proxies), 1.6.0 (1 proxies)

ん?install前はこうだったから:

❯ bin/istioctl version                                     
client version: 1.5.4
control plane version: 1.5.4
data plane version: 1.5.4 (7 proxies)

control plane version のところが pilot versionistiod version に分かれた。へー。

ingress-gateway が更新されるのはそういうものみたいね:

https://github.com/istio/istio/issues/23923#issuecomment-630892188

## この時点では

Data Planeは1.5.4のまま。リスタートをしても、1.5.4のままのはず。やってみよう。

❯ kubectl rollout restart deployment
deployment.apps/details-v1 restarted
deployment.apps/productpage-v1 restarted
deployment.apps/ratings-v1 restarted
deployment.apps/reviews-v1 restarted
deployment.apps/reviews-v2 restarted
deployment.apps/reviews-v3 restarted
     
❯ bin/istioctl version   
client version: 1.6.0
pilot version: 1.5.4
istiod version: 1.6.0
data plane version: 1.5.4 (6 proxies), 1.6.0 (1 proxies)

1.5.4のままだね。よかった。

## Data Planeの更新

今試したみたいに、新たに istiod-canary を入れただけだと、まだ各Podには旧バージョンのproxyがinjectされる。新しいバージョンを使うためにはnamespaceの設定を変更する必要がある。

❯ kubectl label namespace default istio-injection- istio.io/rev=canary
namespace/default labeled

❯ kubectl get namespaces default --show-labels
NAME      STATUS   AGE   LABELS
default   Active   48m   istio.io/rev=canary

僕がbookinfoをインストールしたdefault namespace から istio-injection ラベルを消して istio.io/rev=canary のラベルを新たに設定した。istio-injection のラベルを消す理由は、そっちの方がistio.io/rev のラベルより優先度が高くなってるかららしい。後方互換性のために。

これでリスタートをすると新しいバージョンを見に行くはず!

❯ kubectl rollout restart deployment
deployment.apps/details-v1 restarted
deployment.apps/productpage-v1 restarted
deployment.apps/ratings-v1 restarted
deployment.apps/reviews-v1 restarted
deployment.apps/reviews-v2 restarted
deployment.apps/reviews-v3 restarted

ポッドが再起動するのを待ってバージョンを確認すると:

❯ bin/istioctl version
client version: 1.6.0
pilot version: 1.5.4
istiod version: 1.6.0
data plane version: 1.6.0 (7 proxies)

Data Planeのところ更新されてた。わーい。一個だけ向き先を見てみると:

❯ bin/istioctl proxy-config endpoints details-v1-7bc48f795d-2fwzr --cluster xds-grpc -ojson | grep hostname
                "hostname": "istiod-canary.istio-system.svc",

canaryバージョンを見てた。

### ふと疑問

Canary upgradeについてはここまでなんだけど、ちょっと疑問に思ったことがある。新しいバージョンをデプロイするときにsetオプションでrevisionを指定したけど、それって設定ファイル(今回だとconfig.yaml)に書けないのかな?書ける方が嬉しいよなと思った。ので試してみた。

結論から言うと、こんな風に書いたらOKだった。

apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  profile: default

  revision: "v160"

  addonComponents:
    prometheus:
      enabled: false

  values:
# 省略

最初は v1.6.0 って書いたんだけどそれだとエラーになった。ピリオドがダメなんだろうな:

❯ bin/istioctl install -f config.yaml
Error: failed to apply manifests: errors occurred during operation

## 気になるところ

### (1) 旧バージョンをどうやって削除する?

istiodとかsidecar-injectorとかが残ってるけど、これ普通に削除したらいいんかな?他にも残ってるリソースありそう?

❯ kubectl get pods -n istio-system
NAME                                   READY   STATUS    RESTARTS   AGE
istio-ingressgateway-ccbb848f5-b94mh   1/1     Running   0          62m
istiod-6558b7b7f4-9d2dk                1/1     Running   0          68m
istiod-canary-848948494f-vrpz8         1/1     Running   0          62m

❯ kubectl get mutatingwebhookconfigurations
NAME                                                 CREATED AT
istio-sidecar-injector                               2020-05-23T13:57:59Z
istio-sidecar-injector-canary                        2020-05-23T14:03:42Z
pod-ready.config.common-webhooks.networking.gke.io   2020-05-23T13:49:48Z

実際に手でistiodのdeploymentを削除してから、もう一度違うバージョンを入れてみようとしたらエラーがでて、見た感じvalidationのhookがistiodを参照してるみたい?元Galleyが持ってた機能かな?

❯ bin/istioctl install -f config.yaml                               
✔ Istio core installed                                                                                                                                                           
2020-05-23T15:31:02.866368Z     error   installer       failed to create "EnvoyFilter/istio-system/metadata-exchange-1.6-v160-2": Internal error occurred: failed calling webhook "validation.istio.io": Post https://istiod.istio-system.svc:443/validate?timeout=30s: no endpoints available for service "istiod"
2020-05-23T15:31:03.008138Z     error   installer       failed to create "EnvoyFilter/istio-system/stackdriver-filter-1.6-v160-2": Internal error occurred: failed calling webhook "validation.istio.io": Post https://istiod.istio-system.svc:443/validate?timeout=30s: no endpoints available for service "istiod"
2020-05-23T15:31:03.164115Z     error   installer       failed to create "EnvoyFilter/istio-system/tcp-metadata-exchange-1.6-v160-2": Internal error occurred: failed calling webhook "validation.istio.io": Post https://istiod.istio-system.svc:443/validate?timeout=30s: no endpoints available for service "istiod"
✘ Istiod encountered an error: failed to create "EnvoyFilter/istio-system/metadata-exchange-1.6-v160-2": Internal error occurred: failed calling webhook "validation.istio.io": Post https://istiod.istio-system.svc:443/validate?timeout=30s: no endpoints available for service "istiod"
failed to create "EnvoyFilter/istio-system/stackdriver-filter-1.6-v160-2": Internal error occurred: failed calling webhook "validation.istio.io": Post https://istiod.istio-system.svc:443/validate?timeout=30s: no endpoints available for service "istiod"
failed to create "EnvoyFilter/istio-system/tcp-metadata-exchange-1.6-v160-2": Internal error occurred: failed calling webhook "validation.istio.io": Post https://istiod.istio-system.svc:443/validate?timeout=30s: no endpoints available for service "istiod"

### (2) canaryって名前を使い続ける?

revisionにcanaryって名前をつけたけど、このやり方だと、このまま使い続けることになりそう。使い続けること前提のrevision名の方が良さそう?v160とか?

### (3) pod単位での更新はできなさそう?

このやり方だとnamaspace単位での設定変更になる。pod単位ではできなさそうだな。pod単位でできた方が良いような気もするけど、namespace単位で十分といえばそういう気もしてきた。

## まとめ

  • 複数バージョンの istiod をインストールして、順次そちらを利用するように切り替えていくのは便利そうではある。
  • IngressGatewayが更新されてしまうのでそこに注意する。
  • クリーンアップ系が整ってくるのを1.7あたりに期待しておいたら良さそう。

このIssueはSubscribeしておこうかな

github.com

面白かった!

Istio 1.6.0 のアップグレード機能をお試し(In place upgrades編)

ちょっと前に「Istio 1.5が出たなぁ」って少し触ってみたんだけど。

bufferings.hatenablog.com

そうこうしてるうちに、もう1.6が出てしまった。3ヶ月ってはやいな。

Istio / Announcing Istio 1.6

そう。1.6が出たってことは1.5もあと3ヶ月の命。そんな風にライフサイクルが短いからIstio自体のアップグレードが大変だよなぁと思っているんだけど、今回、コントロールプレーンのアップグレード方法が改善されたみたい。カナリアリリースもサポートしたよって書いてある。面白そう。

Istio / Safely Upgrade Istio using a Canary Control Plane Deployment

ということで、触ってみようと思う。

## 環境

GKE立ち上げた。

❯ kubectl get nodes
NAME                                       STATUS   ROLES    AGE   VERSION
gke-cluster-1-default-pool-019aa731-6ztc   Ready    <none>   21m   v1.16.8-gke.15
gke-cluster-1-default-pool-019aa731-spbj   Ready    <none>   21m   v1.16.8-gke.15
gke-cluster-1-default-pool-019aa731-ztjz   Ready    <none>   21m   v1.16.8-gke.15

インストール用の設定ファイルはこんな感じにしておいた。Stackdriverの連携をしてるだけ。今回はStackdriver使わんからあんまり関係ないけど。

apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  profile: default

  addonComponents:
    prometheus:
      enabled: false

  values:
    global:
      proxy:
        tracer: "stackdriver"
      logAsJson: true
    telemetry:
      v2:
        enabled: true
        prometheus:
          enabled: false
        stackdriver:
          enabled: true
          logging: true
          monitoring: true
          # topology: true
          configOverride: {}

## 2通りのアップグレード方法

ここを読みながらやってみる:

Istio / Upgrade Istio

2つのアップグレード方法が紹介されてる。

  • Canary upgrades
  • In place upgrades

In place upgradesを最初にやってみて、その後Canary upgradesをやろうと思う。アップグレード前のバージョンはv1.5.4にしようかな。そこからv1.6.0へのアップグレードを試してみよう。

## (1) In place upgrades

まずはIn place upgradesから。この方法は以前からあったのかもしれない?

前提条件は:

  • 現在のIstioのバージョンが1.4.4以降であること
  • 現在のIstioがistioctlでインストールされていること

### Install Istio 1.5

じゃ、1.5を入れる。

❯ curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.5.4 sh - 

❯ cd istio-1.5.4

# 上で書いた設定を config.yaml という名前で置いておいた
❯ bin/istioctl manifest apply -f config.yaml                   
- Applying manifest for component Base...
✔ Finished applying manifest for component Base.
- Applying manifest for component Pilot...
✔ Finished applying manifest for component Pilot.
  Waiting for resources to become ready...
  Waiting for resources to become ready...
  Waiting for resources to become ready...
  Waiting for resources to become ready...
  Waiting for resources to become ready...
  Waiting for resources to become ready...
- Applying manifest for component IngressGateways...
✔ Finished applying manifest for component IngressGateways.


✔ Installation complete

それから、いつものbookinfoアプリを入れる。

# サイドカーの自動インジェクションを有効に
❯ kubectl label namespace default istio-injection=enabled
namespace/default labeled

# Bookinfoサービスをデプロイ
❯ kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml
service/details created
serviceaccount/bookinfo-details created
deployment.apps/details-v1 created
service/ratings created
serviceaccount/bookinfo-ratings created
deployment.apps/ratings-v1 created
service/reviews created
serviceaccount/bookinfo-reviews created
deployment.apps/reviews-v1 created
deployment.apps/reviews-v2 created
deployment.apps/reviews-v3 created
service/productpage created
serviceaccount/bookinfo-productpage created
deployment.apps/productpage-v1 created

# Gatewayをデプロイ
❯ kubectl apply -f samples/bookinfo/networking/bookinfo-gateway.yaml
gateway.networking.istio.io/bookinfo-gateway created
virtualservice.networking.istio.io/bookinfo created

# LBのIPアドレスを取得export INGRESS_HOST=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

# ブラウザで開く(Ubuntu)
❯ xdg-open http://$INGRESS_HOST/productpage

見慣れたページが表示された。

### Upgrade to Istio 1.6

注意点として「トラフィック断が発生する可能性があるから、断を最小化するためにそれぞれのコンポーネントを少なくとも2つ以上動かしておいて PodDisruptionBudgets で1個は起動し続けるようにしておきましょう」って書いてある。今回は、そういうの何にもしてない。

1.6をダウンロード:

❯ curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.6.0 sh - 

❯ cd istio-1.6.0

# 上で書いた設定を config.yaml という名前で置いておいた

このistio-1.6.0に入ってる istioctl を使う。まずはこのistioctlがサポートしてるバージョンの確認

❯ bin/istioctl manifest versions

Binary version is 1.6.0.

This version of istioctl can:
  Install Istio 1.6.0
  Update Istio from >=1.5.0 to 1.6.0
  Update Istio from  <1.7 to 1.6.0

大丈夫だね。それと今のバージョンを確認しておこうか:

❯ bin/istioctl version          
client version: 1.6.0
control plane version: 1.5.4
data plane version: 1.5.4 (7 proxies)

では、アップグレード:

❯ bin/istioctl upgrade -f config.yaml 
2020-05-23T04:57:09.273649Z     info    proto: tag has too few fields: "-"
Control Plane - ingressgateway pod - istio-ingressgateway-7f76b9fc9f-66rv4 - version: 1.5.4
Control Plane - pilot pod - istiod-6558b7b7f4-v2jhw - version: 1.5.4                                      

2020-05-23T04:57:10.295956Z     warn    found 6 CRD of unsupported v1alpha1 security policy: [clusterrbacconfigs.rbac.istio.io meshpolicies.authentication.istio.io policies.authentication.istio.io rbacconfigs.rba
c.istio.io servicerolebindings.rbac.istio.io serviceroles.rbac.istio.io]. The v1alpha1 security policy is no longer supported starting 1.6. It's strongly recommended to delete the CRD of the v1alpha1 security pol
icy to avoid applying any of the v1alpha1 security policy in the unsupported version
Upgrade version check passed: 1.5.4 -> 1.6.0.

しばらく待つと、差分が表示された:

Upgrade check: Warning!!! The following IOPS will be changed as part of upgrade. Please double check they are correct:
components:                                          
  galley: map[enabled:false k8s:map[replicaCount:1 resources:map[requests:map[cpu:100m]]
    strategy:map[rollingUpdate:map[maxSurge:100% maxUnavailable:25%]]]] ->
  ingressGateways:                                   
    '[#0]':                                          
      k8s:                                           
        service:                                     
          ports:                                     
            '[#0]':                                  
              port: 15020 -> 15021                   
              targetPort: 15020 -> 15021                                                                  
            '[#1]':                                  
              targetPort: 80 -> 8080                                                                      
            '[#2]':                                  
              targetPort: -> 8443                    
            '[3->?]': map[name:kiali port:15029 targetPort:15029] ->                                      
            '[4->?]': map[name:prometheus port:15030 targetPort:15030] ->
            '[5->?]': map[name:grafana port:15031 targetPort:15031] ->                                    
            '[6->?]': map[name:tracing port:15032 targetPort:15032] ->                                    
            '[8->?]': map[name:tcp port:31400] ->                                                         
  istiodRemote: -> map[enabled:false]                                                                     
  nodeAgent: map[enabled:false] ->                   
  pilot:                                             
    k8s:                                             
      readinessProbe:                                
        initialDelaySeconds: 5 -> 1                                                                       
        periodSeconds: 5 -> 3                        
      resources: map[requests:map[cpu:500m memory:2048Mi]] ->                                             
  sidecarInjector: map[enabled:false k8s:map[replicaCount:1 strategy:map[rollingUpdate:map[maxSurge:100%
    maxUnavailable:25%]]]] ->                        
installPackagePath: /tmp/istio-install-packages/istio-1.5.4/install/kubernetes/operator
  ->                                                 
meshConfig: -> map[enablePrometheusMerge:false]                                                           
values:                                                                                                                                                                                                             
  base: -> map[validationURL:]                                                                                                                                                                                      
  galley: map[enableAnalysis:false image:galley] ->                                                                                                                                                                 
  gateways:                                                                                                                                                                                                         
    istio-ingressgateway:                                                                                                                                                                                           
      meshExpansionPorts:                                                                                                                                                                                           
        '[#3]':                                                                                                                                                                                                     
          targetPort: 853 -> 8853                                                                                                                                                                                   
      sds: map[enabled:false image:node-agent-k8s resources:map[limits:map[cpu:2000m                                                                                                                                
        memory:1024Mi] requests:map[cpu:100m memory:128Mi]]] ->                                                                                                                                                     
      zvpn:                                                                                                                                                                                                         
        enabled: false ->                                                                                                                                                                                           
        suffix: global ->                                                                                                                                                                                           
  global:                                                                                                                                                                                                           
    disablePolicyChecks: true ->                                                                                                                                                                                    
    enableTracing: true ->                                                                                                                                                                                          
    imagePullPolicy: IfNotPresent ->                                                                                                                                                                                
    istiod:                                                                                                                                                                                                         
      enableAnalysis: -> false                                                                                                                                                                                      
    k8sIngress: map[enableHttps:false enabled:false gatewayName:ingressgateway] ->                                                                                                                                  
    localityLbSetting: map[enabled:true] ->                                                                                                                                                                         
    mtls: map[auto:true enabled:false] ->                                                                                                                                                                           
    outboundTrafficPolicy: map[mode:ALLOW_ANY] ->                                                                                                                                                                   
    policyCheckFailOpen: false ->                                                                                                                                                                                   
    proxy:                                                                                                                                                                                                          
      accessLogEncoding: TEXT ->                                                                                                                                                                                    
      concurrency: 2 ->                                                                                                                                                                                             
      dnsRefreshRate: 300s ->                                                                                                                                                                                       
      envoyAccessLogService: map[enabled:false host:<nil> port:<nil>] ->                                                                                                                                            
      envoyMetricsService: map[enabled:false host:<nil> port:<nil> tcpKeepalive:map[interval:10s                                                                                                                    
        probes:3 time:10s] tlsSettings:map[caCertificates:<nil> clientCertificate:<nil>
        mode:DISABLE privateKey:<nil> sni:<nil> subjectAltNames:[]]] ->
      includeInboundPorts: '* ->'                    
      protocolDetectionTimeout: 100ms ->                                                                  
    sds:                                             
      enabled: false ->                              
    tracer:                                          
      lightstep:                                     
        secure: true ->                              
  grafana:                                                                                                                                                                                                          
    ingress: map[annotations:<nil> enabled:false hosts:[grafana.local] tls:<nil>]                                                                                                                                   
      ->                                                                                                                                                                                                            
  istiodRemote: -> map[injectionURL:]                                                                                                                                                                               
  kiali:                                                                                                                                                                                                            
    dashboard:                                                                                                                                                                                                      
      auth: -> map[strategy:login]                                                                                                                                                                                  
    ingress: map[annotations:<nil> enabled:false hosts:[kiali.local] tls:<nil>] ->
    service: -> map[annotations:map[]]                                                                    
    tag: v1.15 -> v1.18                              
  mixer:                                             
    telemetry:                                       
      reportBatchMaxEntries: 100 ->                                                                       
      reportBatchMaxTime: 1s ->                      
  nodeagent: map[image:node-agent-k8s] ->                                                                 
  pilot:
    enableProtocolSniffingForInbound: false -> true
    ingress: map[ingressClass:istio ingressControllerMode:STRICT ingressService:istio-ingressgateway]
      ->
    meshNetworks: map[networks:map[]] ->
  prometheus:
    ingress: map[annotations:<nil> enabled:false hosts:[prometheus.local] tls:<nil>]
      ->
  security: map[dnsCerts:map[istio-pilot-service-account.istio-control:istio-pilot.istio-control]
    enableNamespacesByDefault:true image:citadel selfSigned:true] ->
  sidecarInjectorWebhook:
    image: sidecar_injector ->
    selfSigned: false ->
  tracing:
    ingress: map[annotations:<nil> enabled:false hosts:<nil> tls:<nil>] ->
    zipkin:
      probeStartupDelay: 200 -> 10
      resources:
        limits:
          cpu: 300m -> 1000m
          memory: 900Mi -> 2048Mi
      tag: 2.14.2 -> 2.20.0

Confirm to proceed [y/N]?

めっちゃ差分でるやん・・・。とりあえず y 押しとこか。

- Processing resources for Istio core.
✔ Istio core installed
✔ Istiod installed
✔ Ingress gateways installed
✔ Installation complete
Upgrade rollout completed. All Istio control plane pods are running on the target version.


Control Plane - ingressgateway pod - istio-ingressgateway-669dfc8fc4-b6dvz - version: 1.6.0
Control Plane - ingressgateway pod - istio-ingressgateway-669dfc8fc4-zbhtk - version: 1.6.0
Control Plane - pilot pod - istiod-564bb6fbc6-r9dpf - version: 1.6.0

Success. Now the Istio control plane is running at version 1.6.0.

To upgrade the Istio data plane, you will need to re-inject it.
If you’re using automatic sidecar injection, you can upgrade the sidecar by doing a rolling update for all the pods:
    kubectl rollout restart deployment --namespace <namespace with auto injection>
If you’re using manual injection, you can upgrade the sidecar by executing:
    kubectl apply -f < (istioctl kube-inject -f <original application deployment yaml>)

となりでwatchしてたのはこんな感じ。istiodとistio-ingressgatewayが更新されてる。

istio-system   istiod-564bb6fbc6-r9dpf                                     0/1     Pending   0          0s
istio-system   istiod-564bb6fbc6-r9dpf                                     0/1     Pending   0          0s
istio-system   istiod-564bb6fbc6-r9dpf                                     0/1     ContainerCreating   0          0s
istio-system   istiod-564bb6fbc6-r9dpf                                     0/1     Running             0          10s
istio-system   istiod-564bb6fbc6-r9dpf                                     1/1     Running             0          10s
istio-system   istiod-6558b7b7f4-v2jhw                                     1/1     Terminating         0          60m
istio-system   istiod-6558b7b7f4-v2jhw                                     0/1     Terminating         0          60m
istio-system   istio-ingressgateway-669dfc8fc4-b6dvz                       0/1     Pending             0          0s
istio-system   istio-ingressgateway-669dfc8fc4-b6dvz                       0/1     Pending             0          0s
istio-system   istio-ingressgateway-669dfc8fc4-b6dvz                       0/1     ContainerCreating   0          0s
istio-system   istio-ingressgateway-669dfc8fc4-b6dvz                       0/1     Running             0          8s
istio-system   istio-ingressgateway-669dfc8fc4-b6dvz                       1/1     Running             0          8s
istio-system   istio-ingressgateway-7f76b9fc9f-66rv4                       1/1     Terminating         0          60m
istio-system   istiod-6558b7b7f4-v2jhw                                     0/1     Terminating         0          61m
istio-system   istiod-6558b7b7f4-v2jhw                                     0/1     Terminating         0          61m
istio-system   istio-ingressgateway-7f76b9fc9f-66rv4                       0/1     Terminating         0          60m
istio-system   istio-ingressgateway-669dfc8fc4-zbhtk                       0/1     Pending             0          0s
istio-system   istio-ingressgateway-669dfc8fc4-zbhtk                       0/1     Pending             0          0s
istio-system   istio-ingressgateway-669dfc8fc4-zbhtk                       0/1     ContainerCreating   0          0s
istio-system   istio-ingressgateway-7f76b9fc9f-66rv4                       0/1     Terminating         0          60m
istio-system   istio-ingressgateway-7f76b9fc9f-66rv4                       0/1     Terminating         0          60m
istio-system   istio-ingressgateway-669dfc8fc4-zbhtk                       0/1     Running             0          8s
istio-system   istio-ingressgateway-669dfc8fc4-zbhtk                       1/1     Running             0          9s

これでControl Planeの更新は終わり。次はData Plane。まだ今はこんな感じ:

❯ bin/istioctl version
client version: 1.6.0
control plane version: 1.6.0
data plane version: 1.6.0 (1 proxies), 1.5.4 (6 proxies)

Data Planeでいっこだけ1.6.0なのはIngressGatewayのことだと思う。では、ロールアウト:

❯ kubectl rollout restart deployment
deployment.apps/details-v1 restarted
deployment.apps/productpage-v1 restarted
deployment.apps/ratings-v1 restarted
deployment.apps/reviews-v1 restarted
deployment.apps/reviews-v2 restarted
deployment.apps/reviews-v3 restarted

しばらく待つと全部1.6になった。

❯ bin/istioctl version
client version: 1.6.0
control plane version: 1.6.0
data plane version: 1.6.0 (7 proxies)

Bookinfoも動いてる。

## (1-2) In place downgrades

ダウングレードもサポートされてるっぽい。ダウングレード先のバージョンのistioctlを使う。

cd istio-1.5.4

❯ bin/istioctl manifest versions

Operator version is 1.5.4.

The following installation package versions are recommended for use with this version of the operator:
  1.5.0

The following installation package versions are supported for upgrade by this version of the operator:
  >=1.4.0
   <1.6

1.6からのダウングレードは大丈夫みたいね。今はこういう状態:

❯ bin/istioctl version
client version: 1.5.4
control plane version: 1.6.0
data plane version: 1.6.0 (7 proxies)

適用してみよう。ドキュメントには experimental をつけてねって書いてあるけど、それは1.4の場合っぽいな。1.5でやってみたら「もう卒業したから外してね!」って言われた。ということで、外して実行!

❯ bin/istioctl upgrade -f config.yaml                                                                                                                                                                              
Control Plane - ingressgateway pod - istio-ingressgateway-669dfc8fc4-b6dvz - version: 1.6.0
Control Plane - pilot pod - istiod-564bb6fbc6-r9dpf - version: 1.6.0

2020-05-23T05:34:52.325447Z     info    Error: upgrade version check failed: 1.6.0 -> 1.5.4. Error: upgrade is currently not supported: 1.6.0 -> 1.5.4

Error: upgrade version check failed: 1.6.0 -> 1.5.4. Error: upgrade is currently not supported: 1.6.0 -> 1.5.4

(´・ω・`)ショボーン サポートされてないって言われたwどういうことなの?

1.5にダウングレードしたらそのままCanary upgradesをやろうと思ってたけど、ちょうどいいし今回はここまでにしてクラスターをシャットダウンしとこ。Canary upgradesは次回。

追記

読み直してみて気づいたけど、ダウングレードのサポートのとこ

  <1.6

って書いてあるから1.6は含まれてないやん!そっか。1.6系から、1.5系には戻せないってことだな。

種蒔きをして楽しみにしてる

ふと。

僕らは種蒔きをしてるよなぁって思った。

芽が出るかどうかは分かんないけど、ここに芽が出ると面白いなぁって思うようなところに種を蒔いてる。

これからくるかどうかは分かんない技術の勉強をしてみたり。

いつか役にたつといいなぁと思ってXPの本を読んでみたり。

もっと良いコードが書きたいなってTDDの素振りをしてみたり。

そういうのもあれば。

今はまだ分かんないかもしれないし、僕が言うことが正しいかどうかも分からないけど、いつか役に立つといいなと思って言葉を伝えてみたり。

もしこのまま進むと、この辺で壁にぶつかるかもしれないから、ちょっとお土産を置いといてみたり。

そういうのもある。

芽が出るといいなぁとは思っているけど、出なくてもそれはそれでいいかなと思う。

そういうので蒔くのをやめちゃうと、そもそも芽が出ることもないし。

いっぱい蒔いておいて、忘れた頃に色々つながったりして、それが自分を助けてくれたり、また次の種を蒔くためのきっかけをくれたりして。

そういう風に種蒔きをして楽しみにしてる。