<p>I made a Kubernetes cluster one year ago with an external etcd cluster (3 members).</p>

<p>A the time, I did not know that it was possible to make an etcd internal, so I made an external cluster and connected Kubernetes to it.</p>

<p>Now I am seeing that an internal cluster is a thing and it is a cleaner solution because the etcd nodes are updated when you update your Kubernetes cluster.</p>

<p>I can't find a clean solution to migrate an external etcd cluster to an internal cluster. I hope there is a solution with zero downtime. Do you know if it is possible please ?</p>

<p>Thank you for your response and have a nice day !</p>

Nurza
<p>As I can understand you have 3 etcd cluster members, external from Kubernetes cluster perspective. The expected outcome is to have all three members running on Kubernetes master nodes.
There is some information left undisclosed, so I try to explain several possible options.</p>
<p>First of all, there are several reasonable ways to run etcd process to use as Kubernetes control-plane key-value storage:</p>
<ul>
<li>etcd run as static pod, having startup configuration in <code>/etc/kubernetes/manifests/etcd.yaml</code> file</li>
<li>etcd run as a system service defined in <code>/etc/systemd/system/etcd.service</code> or similar file</li>
<li>etcd run as a docker container configured using command line options. (this solution is not really safe, unless you can make the contaner restarted after failure or host reboot)</li>
</ul>
<p>For experimental purposes, you can also run etcd:</p>
<ul>
<li>as a simple process in linux userspace</li>
<li>as a stateful set in the kubernetes cluster</li>
<li>as a etcd cluster managed by <a href="https://github.com/coreos/etcd-operator/" rel="nofollow noreferrer">etcd-operator</a>.</li>
</ul>
<p>My personal recommendation is to have 5 members etcd cluster: 3 members runs as a static pods on 3 master kubernetes nodes and two more runs as static pods on external (Kubernetes cluster independent) hosts. In this case you will still have a quorum if you have at least one master node running or if you loose two external nodes by any reason.</p>
<p>There are at least two way to migrate etcd cluster from external instances to the Kubernetes cluster master nodes. It works in the opposite way too.</p>
<h2>Migration</h2>
<p>It's quite straighforward way to migrate the cluster. During this procedure members are turned off (one at a time), moved to another host and started again. Your cluster shouldn't have any problems while you still have quorum in the etcd cluster. My recommendation is  to have at least 3 or better 5 nodes etcd cluster to make the migration safer. For bigger clusters it's may be more convenient to use the other solution from my second answer.</p>
<p>The process of moving etcd member to another IP address is described in the <a href="https://github.com/etcd-io/etcd/blob/a621d807f061e1dd635033a8d6bc261461429e27/Documentation/v2/admin_guide.md#member-migration" rel="nofollow noreferrer">official documentation</a>:</p>
<blockquote>
<p>To migrate a member:</p>
<ol>
<li>Stop the member process.</li>
<li>Copy the data directory of the now-idle member to the new machine.</li>
<li>Update the peer URLs for the replaced member to reflect the new machine according to the runtime reconfiguration instructions.</li>
<li>Start etcd on the new machine, using the same configuration and the copy of the data directory.</li>
</ol>
</blockquote>
<p>Now let's look closer on each step:</p>
<p><strong>0.1 Ensure your etcd cluster is healthy</strong> and all members are in a good condition. I would recommend also checking the logs of all etcd members, just in case.</p>
<p>(To successfuly run the following commands please refer to step 3 for auth variables and aliases)</p>
<pre><code># last two commands only show you members specified by using --endpoints command line option
# the following commands is suppose to run with root privileges because certificates are not accessible by regular user

e2 cluster-health
e3 endpoint health
e3 endpoint status
</code></pre>
<p><strong>0.2 Check each etcd member configuration</strong> and find out where etcd data-dir is located, then ensure that it will remain accessible after etcd process termination. In most cases it's located under /var/lib/etcd on the host machine and used directly or mounted as a volume to etcd pod or docker container.</p>
<p><strong>0.3 <a href="https://github.com/etcd-io/etcd/blob/a621d807f061e1dd635033a8d6bc261461429e27/Documentation/op-guide/recovery.md#snapshotting-the-keyspace" rel="nofollow noreferrer">Create a snapshot</a> of each etcd cluster member</strong>, it's better don't use it, than don't have it.</p>
<p><strong>1. Stop etcd member process.</strong></p>
<p>If you use <code>kubelet</code> to start etcd, as recommended <a href="https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/setup-ha-etcd-with-kubeadm/#setting-up-the-cluster" rel="nofollow noreferrer">here</a>, move  <code>etcd.yaml</code> file out of <code>/etc/kubernetes/manifests/</code>. Right after that etcd Pod will be terminated by <code>kubelet</code>:</p>
<pre><code>sudo mv /etc/kubernetes/manifests/etcd.yaml ~/
sudo chmod 644 ~/etcd.yaml 
</code></pre>
<p>In case if you start etcd process <a href="https://github.com/coreos/etcd-operator/" rel="nofollow noreferrer">as a systemd service</a> you can stop it using the following command:</p>
<pre><code>sudo systemctl stop etcd-service-name.service
</code></pre>
<p>In case of docker container you can stop it using the following command:</p>
<pre><code>docker ps -a 
docker stop &lt;etcd_container_id&gt;
docker rm &lt;etcd_container_id&gt;
</code></pre>
<p>If you run the etcd process from the command line, you can kill it using the following command:</p>
<pre><code>kill `pgrep etcd`
</code></pre>
<p><strong>2. Copy the data directory of the now-idle member to the new machine.</strong></p>
<p>Not much complexity here. Compact etcd data-dir to the file and copy it to the destination instance. I also recommend to copy etcd manifest or systemd service configuration if you plan to run etcd on the new instance in the same way.</p>
<pre><code>tar -C /var/lib -czf etcd-member-name-data.tar.gz etcd
tar -czf etcd-member-name-conf.tar.gz [etcd.yaml] [/etc/systemd/system/etcd.service]  [/etc/kubernetes/manifests/etcd.conf ...]
scp etcd-member-name-data.tar.gz destination_host:~/
scp etcd-member-name-conf.tar.gz destination_host:~/
</code></pre>
<p><strong>3. Update the peer URLs for the replaced member</strong> to reflect the new member IP address according to the runtime reconfiguration instructions.</p>
<p>There are two way to do it, by using <code>etcd API</code> or by running <code>etcdctl</code> utility.</p>
<p>That's how <code>etcdctl</code> way may look like:<br />
<em>(replace etcd endpoints variables with the correct etcd cluster members ip addresses)</em></p>
<pre><code># all etcd cluster members should be specified
export ETCDSRV=&quot;--endpoints https://etcd.ip.addr.one:2379,https://etcd.ip.addr.two:2379,https://etcd.ip.addr.three:2379&quot;
#authentication parameters for v2 and v3 etcdctl APIs
export ETCDAUTH2=&quot;--ca-file /etc/kubernetes/pki/etcd/ca.crt --cert-file /etc/kubernetes/pki/etcd/peer.crt --key-file  /etc/kubernetes/pki/etcd/peer.key&quot;
export ETCDAUTH3=&quot;--cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/peer.crt --key=/etc/kubernetes/pki/etcd/peer.key&quot;

# etcdctl API v3 alias
alias e3=&quot;ETCDCTL_API=3 etcdctl $ETCDAUTH3 $ETCDSRV&quot;
# etcdctl API v2 alias
alias e2=&quot;ETCDCTL_API=2 etcdctl $ETCDAUTH2 $ETCDSRV&quot;

# list all etcd cluster members and their IDs
e2 member list

e2 member update member_id http://new.etcd.member.ip:2380
#or
e3 member update member_id --peer-urls=&quot;https://new.etcd.member.ip:2380&quot;
</code></pre>
<p>That's how <code>etcd API</code> way may look like:</p>
<pre><code>export CURL_ETCD_AUTH=&quot;--cert /etc/kubernetes/pki/etcd/peer.crt --key /etc/kubernetes/pki/etcd/peer.key --cacert /etc/kubernetes/pki/etcd/ca.crt&quot;

curl https://health.etcd.istance.ip:2379/v2/members/member_id -XPUT -H &quot;Content-Type: application/json&quot; -d '{&quot;peerURLs&quot;:[&quot;http://new.etcd.member.ip:2380&quot;]}' ${CURL_ETCD_AUTH}
</code></pre>
<p><strong>4. Start etcd on the new machine</strong>, using the adjusted configuration and the copy of the data directory.</p>
<p>Unpack etcd data-dir on the new host:</p>
<pre><code>tar -xzf etcd-member-name-data.tar.gz -C /var/lib/
</code></pre>
<p>Adjust etcd startup configuration according to your needs. At this point it's easy to select another way to run etcd. Depending on your choice prepare manifest or service definition file and replace there old ip address with new. E.g.:</p>
<pre><code>sed -i  's/\/10.128.0.12:/\/10.128.0.99:/g' etcd.yaml
</code></pre>
<p>Now it's time to start etcd by moving <code>etcd.yaml</code> to <code>/etc/kubernetes/manifests/</code>, or by running the following command (if you run <code>etcd</code> as a <code>systemd</code> service)</p>
<pre><code>sudo systemctl start etcd-service-name.service
</code></pre>
<p><strong>5. Check updated etcd process logs and etcd cluster health</strong> to ensure that member is healthy.</p>
<p>To do that you can use the following commands:</p>
<pre><code>$ e2 cluster-health

$ kubectl logs etct_pod_name -n kube-system

$ docker logs etcd_container_id 2&gt;&amp;1 | less

$ journalctl -e -u etcd_service_name     
</code></pre>
