Upgrade etcd from 3.2 to 3.3

    In the general case, upgrading from etcd 3.2 to 3.3 can be a zero-downtime, rolling upgrade:

    • one by one, stop the etcd v3.2 processes and replace them with etcd v3.3 processes
    • after running all v3.3 processes, new features in v3.3 are available to the cluster

    Before , read through the rest of this guide to prepare.

    NOTE: When migrating from v2 with no v3 data, etcd server v3.2+ panics when etcd restores from existing snapshots but no v3 ETCD_DATA_DIR/member/snap/db file. This happens when the server had migrated from v2 with no previous v3 data. This also prevents accidental v3 data loss (e.g. db file might have been moved). etcd requires that post v3 migration can only happen with v3 data. Do not upgrade to newer v3 versions until v3.0 server contains v3 data.

    NOTE: if you enable auth and use lease(lease ttl is small), it has a high probability to encounter that will result in data inconsistency. It is strongly recommended upgrading to 3.2.31+ firstly to fix this problem, and then upgrade to 3.3. In addition, if the user without permission sends a LeaseRevoke request to the 3.3 node during the upgrade process, it may still cause data corruption, so it is best to ensure that your environment doesn’t exist such abnormal calls before upgrading, see #11691 for detail.

    Highlighted breaking changes in 3.3.

    Changed value type of etcd --auto-compaction-retention flag to string

    Changed --auto-compaction-retention flag to with finer granularity. Now that --auto-compaction-retention accepts string values, etcd configuration YAML file auto-compaction-retention field must be changed to string type. Previously, --config-file etcd.config.yaml can have auto-compaction-retention: 24 field, now must be auto-compaction-retention: "24" or auto-compaction-retention: "24h". If configured as --auto-compaction-mode periodic --auto-compaction-retention "24h", the time duration value for --auto-compaction-retention flag must be valid for function in Go.

    Changed etcdserver.EtcdServer.ServerConfig to *etcdserver.EtcdServer.ServerConfig

    etcdserver.EtcdServer has changed the type of its member field *etcdserver.ServerConfig to etcdserver.ServerConfig. And etcdserver.NewServer now takes etcdserver.ServerConfig, instead of *etcdserver.ServerConfig.

    Before and after (e.g. k8s.io/kubernetes/test/e2e_node/services/etcd.go)

    1. import "github.com/coreos/etcd/etcdserver"
    2. type EtcdServer struct {
    3. *etcdserver.EtcdServer
    4. - config *etcdserver.ServerConfig
    5. + config etcdserver.ServerConfig
    6. }
    7. func NewEtcd(dataDir string) *EtcdServer {
    8. - config := &etcdserver.ServerConfig{
    9. + config := etcdserver.ServerConfig{
    10. DataDir: dataDir,
    11. ...
    12. }
    13. return &EtcdServer{config: config}
    14. }
    15. func (e *EtcdServer) Start() error {
    16. var err error
    17. e.EtcdServer, err = etcdserver.NewServer(e.config)
    18. ...

    Added embed.Config.LogOutput struct

    Note that this field has been renamed to embed.Config.LogOutputs in []string type in v3.4. Please see for more details.

    Field LogOutput is added to embed.Config:

    1. package embed
    2. type Config struct {
    3. Debug bool `json:"debug"`
    4. LogPkgLevels string `json:"log-package-levels"`
    5. + LogOutput string `json:"log-output"`
    6. ...

    Before gRPC server warnings were logged in etcdserver.

    1. WARNING: 2017/11/02 11:35:51 grpc: addrConn.resetTransport failed to create client transport: connection error: desc = "transport: Error while dialing dial tcp: operation was canceled"; Reconnecting to {localhost:2379 <nil>}
    2. WARNING: 2017/11/02 11:35:51 grpc: addrConn.resetTransport failed to create client transport: connection error: desc = "transport: Error while dialing dial tcp: operation was canceled"; Reconnecting to {localhost:2379 <nil>}

    From v3.3, gRPC server logs are disabled by default.

    Note that embed.Config.SetupLogging method has been deprecated in v3.4. Please see v3.4 upgrade guide for more details.

    1. import "github.com/coreos/etcd/embed"
    2. cfg := &embed.Config{Debug: false}
    3. cfg.SetupLogging()

    Set embed.Config.Debug field to true to enable gRPC server logs.

    Changed /health endpoint response

    Previously, [endpoint]:[client-port]/health returned manually marshaled JSON value. 3.3 now defines struct.

    Note that in v3.3.0-rc.0, v3.3.0-rc.1, and v3.3.0-rc.2, etcdhttp.Health has boolean type "health" and "errors" fields. For backward compatibilities, we reverted "health" field to string type and removed "errors" field. Further health information will be provided in separate APIs.

    1. $ curl http://localhost:2379/health
    2. {"health":"true"}

    Changed gRPC gateway HTTP endpoints (replaced /v3alpha with /v3beta)

    Before

    1. curl -L http://localhost:2379/v3alpha/kv/put \
    2. -X POST -d '{"key": "Zm9v", "value": "YmFy"}'

    After

    1. -X POST -d '{"key": "Zm9v", "value": "YmFy"}'

    Requests to /v3alpha endpoints will redirect to /v3beta, and /v3alpha will be removed in 3.4 release.

    Changed maximum request size limits

    3.3 now allows custom request size limits for both server and client side. In previous versions(v3.2.10, v3.2.11), client response size was limited to only 4 MiB.

    Server-side request limits can be configured with --max-request-bytes flag:

    1. # limits request size to 1.5 KiB
    2. etcd --max-request-bytes 1536
    3. # client writes exceeding 1.5 KiB will be rejected
    4. # etcdserver: request is too large

    Or configure embed.Config.MaxRequestBytes field:

    1. import "github.com/coreos/etcd/embed"
    2. import "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
    3. // limit requests to 5 MiB
    4. cfg := embed.NewConfig()
    5. cfg.MaxRequestBytes = 5 * 1024 * 1024
    6. // client writes exceeding 5 MiB will be rejected
    7. _, err := cli.Put(ctx, "foo", [LARGE VALUE...])
    8. err == rpctypes.ErrRequestTooLarge

    Client-side request limits must be configured based on server-side limits.

    1. # limits request size to 1 MiB
    2. etcd --max-request-bytes 1048576

    If not specified, client-side send limit defaults to 2 MiB (1.5 MiB + gRPC overhead bytes) and receive limit to math.MaxInt32. Please see clientv3 godoc for more detail.

    Changed raw gRPC client wrapper function signatures

    3.3 changes the function signatures of clientv3 gRPC client wrapper. This change was needed to support .

    Before and after

    1. -func NewKVFromKVClient(remote pb.KVClient) KV {
    2. +func NewKVFromKVClient(remote pb.KVClient, c *Client) KV {
    3. -func NewClusterFromClusterClient(remote pb.ClusterClient) Cluster {
    4. +func NewClusterFromClusterClient(remote pb.ClusterClient, c *Client) Cluster {
    5. -func NewLeaseFromLeaseClient(remote pb.LeaseClient, keepAliveTimeout time.Duration) Lease {
    6. +func NewLeaseFromLeaseClient(remote pb.LeaseClient, c *Client, keepAliveTimeout time.Duration) Lease {
    7. -func NewMaintenanceFromMaintenanceClient(remote pb.MaintenanceClient) Maintenance {
    8. +func NewMaintenanceFromMaintenanceClient(remote pb.MaintenanceClient, c *Client) Maintenance {
    9. -func NewWatchFromWatchClient(wc pb.WatchClient) Watcher {
    10. +func NewWatchFromWatchClient(wc pb.WatchClient, c *Client) Watcher {

    Changed clientv3 Snapshot API error type

    Previously, clientv3 Snapshot API returned raw [grpc/*status.statusError] type error. v3.3 now translates those errors to corresponding public error types, to be consistent with other APIs.

    Before

    1. import "context"
    2. // reading snapshot with canceled context should error out
    3. ctx, cancel := context.WithCancel(context.Background())
    4. rc, _ := cli.Snapshot(ctx)
    5. cancel()
    6. _, err := io.Copy(f, rc)
    7. err.Error() == "rpc error: code = Canceled desc = context canceled"
    8. // reading snapshot with deadline exceeded should error out
    9. ctx, cancel = context.WithTimeout(context.Background(), time.Second)
    10. defer cancel()
    11. rc, _ = cli.Snapshot(ctx)
    12. time.Sleep(2 * time.Second)
    13. _, err = io.Copy(f, rc)
    14. err.Error() == "rpc error: code = DeadlineExceeded desc = context deadline exceeded"

    After

    1. import "context"
    2. // reading snapshot with canceled context should error out
    3. ctx, cancel := context.WithCancel(context.Background())
    4. rc, _ := cli.Snapshot(ctx)
    5. cancel()
    6. _, err := io.Copy(f, rc)
    7. err == context.Canceled
    8. // reading snapshot with deadline exceeded should error out
    9. ctx, cancel = context.WithTimeout(context.Background(), time.Second)
    10. defer cancel()
    11. rc, _ = cli.Snapshot(ctx)
    12. time.Sleep(2 * time.Second)
    13. _, err = io.Copy(f, rc)
    14. err == context.DeadlineExceeded

    Changed etcdctl lease timetolive command output

    Previously, lease timetolive LEASE_ID command on expired lease prints -1s for remaining seconds. 3.3 now outputs clearer messages.

    Before

    1. lease 2d8257079fa1bc0c granted with TTL(0s), remaining(-1s)

    After

    1. lease 2d8257079fa1bc0c already expired

    Changed golang.org/x/net/context imports

    clientv3 has deprecated golang.org/x/net/context. If a project vendors golang.org/x/net/context in other code (e.g. etcd generated protocol buffer code) and imports github.com/coreos/etcd/clientv3, it requires Go 1.9+ to compile.

    Before

    1. import "golang.org/x/net/context"
    2. cli.Put(context.Background(), "f", "v")

    After

    1. import "context"
    2. cli.Put(context.Background(), "f", "v")

    Changed gRPC dependency

    3.3 now requires grpc/grpc-go v1.7.5.

    Deprecated grpclog.Logger

    grpclog.Logger has been deprecated in favor of . clientv3.Logger is now grpclog.LoggerV2.

    Before

    1. import "github.com/coreos/etcd/clientv3"
    2. clientv3.SetLogger(log.New(os.Stderr, "grpc: ", 0))

    After

    1. import "github.com/coreos/etcd/clientv3"
    2. import "google.golang.org/grpc/grpclog"
    3. clientv3.SetLogger(grpclog.NewLoggerV2(os.Stderr, os.Stderr, os.Stderr))
    4. // log.New above cannot be used (not implement grpclog.LoggerV2 interface)
    Deprecated grpc.ErrClientConnTimeout

    Previously, grpc.ErrClientConnTimeout error is returned on client dial time-outs. 3.3 instead returns context.DeadlineExceeded (see #8504).

    Before

    1. // expect dial time-out on ipv4 blackhole
    2. _, err := clientv3.New(clientv3.Config{
    3. DialTimeout: 2 * time.Second
    4. })
    5. if err == grpc.ErrClientConnTimeout {
    6. // handle errors
    7. }

    After

    Changed official container registry

    etcd now uses as a primary container registry, and quay.io/coreos/etcd as secondary.

    Before

    1. docker pull quay.io/coreos/etcd:v3.2.5
    1. docker pull gcr.io/etcd-development/etcd:v3.3.0

    had to include some features from 3.4, while trying to minimize the difference between client balancer implementation. This release fixes “kube-apiserver 1.13.x refuses to work when first etcd-server is not available” (kubernetes#72102).

    grpc.ErrClientConnClosing has been .

    1. import (
    2. + "go.etcd.io/etcd/clientv3"
    3. "google.golang.org/grpc"
    4. + "google.golang.org/grpc/codes"
    5. + "google.golang.org/grpc/status"
    6. )
    7. _, err := kvc.Get(ctx, "a")
    8. -if err == grpc.ErrClientConnClosing {
    9. +if clientv3.IsConnCanceled(err) {
    10. // or
    11. +s, ok := status.FromError(err)
    12. +if ok {
    13. + if s.Code() == codes.Canceled

    The new client balancer uses an asynchronous resolver to pass endpoints to the gRPC dial function. As a result, or later requires grpc.WithBlock dial option to wait until the underlying connection is up.

    1. import (
    2. "time"
    3. "go.etcd.io/etcd/clientv3"
    4. + "google.golang.org/grpc"
    5. )
    6. +// "grpc.WithBlock()" to block until the underlying connection is up
    7. ccfg := clientv3.Config{
    8. Endpoints: []string{"localhost:2379"},
    9. DialTimeout: time.Second,
    10. + DialOptions: []grpc.DialOption{grpc.WithBlock()},
    11. DialKeepAliveTime: time.Second,
    12. DialKeepAliveTimeout: 500 * time.Millisecond,
    13. }

    Please see CHANGELOG for a full list of changes.

    Upgrade requirements

    To upgrade an existing etcd deployment to 3.3, the running cluster must be 3.2 or greater. If it’s before 3.2, please before upgrading to 3.3.

    Also, to ensure a smooth rolling upgrade, the running cluster must be healthy. Check the health of the cluster by using the etcdctl endpoint health command before proceeding.

    Preparation

    Before upgrading etcd, always test the services relying on etcd in a staging environment before deploying the upgrade to the production environment.

    Before beginning, backup the etcd data. Should something go wrong with the upgrade, it is possible to use this backup to back to existing etcd version. Please note that the snapshot command only backs up the v3 data. For v2 data, see backing up v2 datastore.

    Mixed versions

    While upgrading, an etcd cluster supports mixed versions of etcd members, and operates with the protocol of the lowest common version. The cluster is only considered upgraded once all of its members are upgraded to version 3.3. Internally, etcd members negotiate with each other to determine the overall cluster version, which controls the reported version and the supported features.

    Limitations

    Note: If the cluster only has v3 data and no v2 data, it is not subject to this limitation.

    If the cluster is serving a v2 data set larger than 50MB, each newly upgraded member may take up to two minutes to catch up with the existing cluster. Check the size of a recent snapshot to estimate the total data size. In other words, it is safest to wait for 2 minutes between upgrading each member.

    For a much larger total data size, 100MB or more , this one-time process might take even more time. Administrators of very large etcd clusters of this magnitude can feel free to contact the before upgrading, and we’ll be happy to provide advice on the procedure.

    Downgrade

    If all members have been upgraded to v3.3, the cluster will be upgraded to v3.3, and downgrade from this completed state is not possible. If any single member is still v3.2, however, the cluster and its operations remains “v3.2”, and it is possible from this mixed cluster state to return to using a v3.2 etcd binary on all members.

    Please backup the data directory of all etcd members to make downgrading the cluster possible even after it has been completely upgraded.

    This example shows how to upgrade a 3-member v3.2 ectd cluster running on a local machine.

    1. Check upgrade requirements

    Is the cluster healthy and running v3.2.x?

    1. $ ETCDCTL_API=3 etcdctl endpoint health --endpoints=localhost:2379,localhost:22379,localhost:32379
    2. localhost:2379 is healthy: successfully committed proposal: took = 6.600684ms
    3. localhost:22379 is healthy: successfully committed proposal: took = 8.540064ms
    4. localhost:32379 is healthy: successfully committed proposal: took = 8.763432ms
    5. $ curl http://localhost:2379/version
    6. {"etcdserver":"3.2.7","etcdcluster":"3.2.0"}

    2. Stop the existing etcd process

    When each etcd process is stopped, expected errors will be logged by other cluster members. This is normal since a cluster member connection has been (temporarily) broken:

    1. 14:13:31.491746 I | raft: c89feb932daef420 [term 3] received MsgTimeoutNow from 6d4f535bae3ab960 and starts an election to get leadership.
    2. 14:13:31.491769 I | raft: c89feb932daef420 became candidate at term 4
    3. 14:13:31.491788 I | raft: c89feb932daef420 received MsgVoteResp from c89feb932daef420 at term 4
    4. 14:13:31.491797 I | raft: c89feb932daef420 [logterm: 3, index: 9] sent MsgVote request to 6d4f535bae3ab960 at term 4
    5. 14:13:31.491805 I | raft: c89feb932daef420 [logterm: 3, index: 9] sent MsgVote request to 9eda174c7df8a033 at term 4
    6. 14:13:31.491815 I | raft: raft.node: c89feb932daef420 lost leader 6d4f535bae3ab960 at term 4
    7. 14:13:31.524084 I | raft: c89feb932daef420 received MsgVoteResp from 6d4f535bae3ab960 at term 4
    8. 14:13:31.524108 I | raft: c89feb932daef420 [quorum:2] has received 2 MsgVoteResp votes and 0 vote rejections
    9. 14:13:31.524123 I | raft: c89feb932daef420 became leader at term 4
    10. 14:13:31.524136 I | raft: raft.node: c89feb932daef420 elected leader c89feb932daef420 at term 4
    11. 14:13:31.592650 W | rafthttp: lost the TCP streaming connection with peer 6d4f535bae3ab960 (stream MsgApp v2 reader)
    12. 14:13:31.592825 W | rafthttp: lost the TCP streaming connection with peer 6d4f535bae3ab960 (stream Message reader)
    13. 14:13:31.693275 E | rafthttp: failed to dial 6d4f535bae3ab960 on stream Message (dial tcp [::1]:2380: getsockopt: connection refused)
    14. 14:13:31.693289 I | rafthttp: peer 6d4f535bae3ab960 became inactive
    15. 14:13:31.936678 W | rafthttp: lost the TCP streaming connection with peer 6d4f535bae3ab960 (stream Message writer)

    It’s a good idea at this point to to provide a downgrade path should any problems occur:

    1. $ etcdctl snapshot save backup.db

    3. Drop-in etcd v3.3 binary and start the new etcd process

    The new v3.3 etcd will publish its information to the cluster:

    1. 14:14:25.363225 I | etcdserver: published {Name:s1 ClientURLs:[http://localhost:2379]} to cluster a9ededbffcb1b1f1

    Verify that each member, and then the entire cluster, becomes healthy with the new v3.3 etcd binary:

    1. $ ETCDCTL_API=3 /etcdctl endpoint health --endpoints=localhost:2379,localhost:22379,localhost:32379
    2. localhost:22379 is healthy: successfully committed proposal: took = 5.540129ms
    3. localhost:32379 is healthy: successfully committed proposal: took = 7.321771ms
    4. localhost:2379 is healthy: successfully committed proposal: took = 10.629901ms

    Upgraded members will log warnings like the following until the entire cluster is upgraded. This is expected and will cease after all etcd cluster members are upgraded to v3.3:

    1. 14:15:17.071804 W | etcdserver: member c89feb932daef420 has a higher version 3.3.0
    2. 14:15:21.073110 W | etcdserver: the local etcd version 3.2.7 is not up-to-date
    3. 14:15:21.073142 W | etcdserver: member 6d4f535bae3ab960 has a higher version 3.3.0
    4. 14:15:21.073157 W | etcdserver: the local etcd version 3.2.7 is not up-to-date
    5. 14:15:21.073164 W | etcdserver: member c89feb932daef420 has a higher version 3.3.0

    4. Repeat step 2 to step 3 for all other members

    5. Finish

    When all members are upgraded, the cluster will report upgrading to 3.3 successfully:

    1. $ ETCDCTL_API=3 /etcdctl endpoint health --endpoints=localhost:2379,localhost:22379,localhost:32379
    2. localhost:2379 is healthy: successfully committed proposal: took = 2.312897ms
    3. localhost:32379 is healthy: successfully committed proposal: took = 2.517902ms