Always keep a begin|

wtdata

园龄:2年4个月粉丝:3关注:1

一.docker介绍(1)

一、docker 介绍

容器是一种基础工具,指任何可以用于容纳其他物品的工具。而 docker 是一个开源的应用容器引擎。docker 公司位于旧金山,原名叫 dotcloud,底层使用了 Linux 容器技术(LXC:在操作系统里实现资源隔离与限制)。

1.虚拟化的演变:

图片
Hypervisor:一种运行在基础物理服务器和操作系统之间的中间软件层,可允许多个操作系统和应用共享硬件,常见有 vmare、微软的 Hyper-V、思杰的 Xenserver 等。通过上图可以看到虚拟化的部署可以看到在 APP 的使用必须在实现虚拟化的同时需要安装操作系统。例如我不想在宿主机安装 tomcat 这个服务器,想通过虚拟化的技术将 tomcat 安装在虚拟化的 centos 操作系统中,但是需要安装整个 centos 系统。大大的浪费了我们内存、硬盘、CPU 资源。

Container Runtime:通过 linux 内核虚拟化能力管理多个容器,多个容器共享一个操作系统。所以去除了内核的占用空间及运行所需要的耗时,使得容器轻量和极速!而容器化在安装 tomcat 的时候只需要在 centos 极小化的镜像基础上安装 tomcat 依赖和 tomcat 等。极大的节约了硬盘和内存的占用。

比较了上面的两张图我们可以看到,传统的虚拟机必须携带操作系统,本身很小的应用程序却因为携带了操作系统而变得很大,很笨重。Docker是不携带操作系统的,所以docker的应用就非常轻巧。另外在调用宿主机的CPU、内存、磁盘等等资源的时候,拿内存举例。虚拟机是利用Hypervisor去虚拟化内存,整个调用的过程是虚拟内存--->虚拟物理内存---->物理机内存。但是Docker是利用Docker Engine去调用宿主机的资源,这时候过程是虚拟内存---->物理内存。

2.VM 虚拟化技术和 Docker 的区别

2.1、本质上的区别:

VM 是在宿主机的操作系统的基础上创建虚拟层、通过虚拟化出来资源进行安装操作系统,然后在虚拟化的操作系统上安装软件。

Docker 容器:在宿主机的操作系统上创建 Docker 引擎,然后在引擎的基础上安装应用。其实并不是没有操作系统,而是操作系统的提供商会提供一个专门为 docker 发布的极小镜像。

2.2、使用上的区别:

图片

docker 的优势有:一致的运行环境,更轻松的迁移。对进程进行封装隔离,容器与容器之间互不影响,更高效的利用系统资源。docker 将程序以及程序使用的环境直接打包到一起,无论在哪个机器上保持了环境的一致。

3.Docker架构及组件剖析

图片

docker整体结构采用C/S(客户机/服务器)模式,主要由客户端和服务端两大部分组成,客户端负责发送操作指令,服务端负责接收和处理指令。客户端和服务端通信有很多种方式,既可以在同一台机器上通过UNIX套接字进行通信,也可以通过网络连接远程通信。

图片

Docker客户端

Docker客户端其实是一种泛称。其中Docker命令是Docker用户与Docker服务端交互的主要方式。除了使用docker命令的方式,还可以使用直接请求REST API的方式与Docker服务端交互,甚至可以使用各种语言的SDK与Docker服务端进行交互。

Docker服务端

docker服务端是Docker所有后台服务的统称,其中dockerd是一个非常重要的后台管理进程,他负责响应和处理来自Docekr客户端的请求,然后将客户端的请求转换为Docker具体的操作。例如镜像、容器、网络和挂载卷等具体对象的操作管理。

Docker从诞生到现在,服务端历经了多次架构重构。期初,服务端的组件是全部集成在Docker二进制里。但是从1.11版本开始,dockerd已经成为独立的二进制,此时的容器也不是直接由dockerd来启动了,而是集成了containerd、runC等多个组件。

虽然Docker的架构不停重构,但是各个模块的基本功能和定位并没有变化。他和一般的C/S架构系统一样,Docker服务端模块负责和Docker客户端交互,并管理Docker的容器、镜像、网络资源。

Docker组件剖析:

下面,以Docker的20.10.9版本为例,看下Docker都有哪些工具和组件。

1、下载docker二进制文件

wget https://download.docker.com/linux/static/stable/x86_64/docker-20.10.6.tgz

2、解压

tar -zxvf docker-20.10.6.tgz

3、将Docker可执行文件复制到/usr/bin目录下

cp docker/ /usr/bin/  

4、查看有关docker的二进制文件

[root@localhost ~]# ll /usr/bin/
总用量 229716
-rwxr-xr-x. 1 root root 39602024 4月  22 14:21 containerd
-rwxr-xr-x. 1 root root  7270400 4月  22 14:21 containerd-shim
-rwxr-xr-x. 1 root root  9953280 4月  22 14:21 containerd-shim-runc-v2
-rwxr-xr-x. 1 root root 21504072 4月  22 14:21 ctr
-rwxr-xr-x. 1 root root 60074792 4月  22 14:21 docker
-rwxr-xr-x. 1 root root 78940616 4月  22 14:21 dockerd
-rwxr-xr-x. 1 root root   708616 4月  22 14:21 docker-init
-rwxr-xr-x. 1 root root  2928566 4月  22 14:21 docker-proxy
-rwxr-xr-x. 1 root root 14233296 4月  22 14:21 runc

可以看到,Docker目前已经有了非常多的组件和工具。这些组件可以分为三大类:

  • docker相关的组件:docker、dockerd、docker-init、docker-proxy
  • containerd相关的组件:containerd、contarinerd-shim和ctr
  • 容器运行时相关的组件:runc
    下面我们进行逐一讲解:

(1)docker

docker是Docker客户端的一个完整实现,他是一个二进制文件,对用户可见的操作形式为docker命令,通过docker命令可以完成所有的docker客户端与服务端的通信(还可以通过REST API、SDK等多种形式与Docker服务端通信)。

Docker客户端与服务端的交互过程:docker组件向服务端发送请求后,服务端根据请求执行具体的动作并将结果返回给docker客户端,docke客户端解析服务端的返回结果,并将结果通过命令行标准输出展示给用户。这样一次完整的客户端对服务端的请求就完成了。

(2)dockerd

dockerd是docker服务端的后台常驻进程,用来接收客户端发送的请求,执行具体的处理任务,处理完成后将结果返回给客户端。

Docker客户端可以通过多种方式向dockerd发送请求,我们常用的docker客户端与dockerd的交互方式有三种。

  • 通过UNIX套接字与服务端通信:配置格式为unix://socket_path,默认dockerd生成的socket文件路径为/var/run/docker.sock,该文件只有root用户或者docker用户组的用户才可以访问。这就是为什么Docker刚安装完成后只有root用户才能使用docker的原因。
  • 通过TCP与服务端通信:配置格式为TCP://host:port,通过这种方式可以实现客户端远程连接服务端,但是在方便的同时也带有安全隐患,所以在生产环境中如果你要使用TCP的方式与Docker服务端通信,推荐使用TLS认证,可以通过设置Docker的TLS相关参数,来保证数据传输的安全。
  • 通过文件描述符的方式与服务端通信:配置格式为:fd://这种格式一般用于systemd管理的系统中。
    DOcker客户端和服务端的通信方式必须保持一致,否则无法通信,只有当dockerd监听了UNIX套接字客户端才可以使用UNIX套接字的方式与服务端通信,UNIX套接字也是docker默认的通信方式,如果你想要通过远程的方式访问dockerd,可以在dockerd启动的时候添加-H参数指定监听的HOST和PORT。

(3)docker-init

如果你熟悉Linux系统,你应该知道在linux系统中,1号进程是init进程,是所有进程的父进程。主机上的进程出现问题时,init进程可以帮助我们回收这些问题进程。同样的,在容器内部,当我们自己的业务进程没有回收子进程的能力时,在执行docker run启动容器时可以添加--init参数,此时Docker会使用docker-init作为1号子进程,帮你管理容器内子进程。例如回收僵尸进程等。

下面我们通过启动一个busybox容器来演示下:

[root@localhost ~]# docker run -it --name busybox001 busybox sh
/ # ps aux
PID   USER     TIME  COMMAND
    1 root      0:00 sh
    7 root      0:00 ps aux
/ #

可以看到容器启动时如果没有添加--init参数,1号进程就是sh进程,我们使用ctrl+D退出当前容器,重新启动一个容器并添加--init参数,然后看下进程:

[root@localhost ~]# docker run -it --name busybox002 --init busybox
/ # ps aux
PID   USER     TIME  COMMAND
    1 root      0:00 /sbin/docker-init -- sh
    7 root      0:00 sh
    8 root      0:00 ps aux

可以看到此时容器内的1号进程已经变为了/sbin/docker-init,而不是sh了。
(4)docker-proxy

docker-proxy主要是用来做端口映射的。当我们使用docker run命令启动容器时,如果使用了-p参数,docker-proxy组件就会把容器内相应的端口映射到主机上来,底层时依赖于iptables实现的。

下面我们通过一个实例演示:

使用以下命令启动一个nginx容器并将容器的80端口映射到主机的8080端口。

docker run -dit --name nginx002 -p 8080:80 nginx

使用以下命令查看下容器的IP地址:

[root@localhost ~]# docker inspect --format '{{ .NetworkSettings.IPAddress }}' nginx002
172.17.0.2

此时,使用ps命令查看一下主机上是否有docker-proxy进程:

[root@localhost ~]# ps aux | grep docker-proxy
root       4664  0.0  0.0 899228  2644 ?        Sl   15:20   0:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8080 -container-ip 172.17.0.2 -container-port 80
root       4672  0.0  0.0 898972  2388 ?        Sl   15:20   0:00 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 8080 -container-ip 172.17.0.2 -container-port 80
root       4803  0.0  0.0 112676   988 pts/0    S+   15:22   0:00 grep --color=auto docker-proxy

可以看到当我们启动一个容器时需要端口映射时, Docker 为我们创建了一个 docker-proxy 进程,并且通过参数把我们的容器 IP 和端口传递给 docker-proxy 进程,然后 docker-proxy 通过 iptables 实现了 nat 转发。
我们通过以下命令查看一下主机上 iptables nat 表的规则:

[root@localhost ~]# iptables -L -nv -t nat | grep docker
    0     0 MASQUERADE  all  --  *      !docker0  172.17.0.0/16        0.0.0.0/0
    0     0 RETURN     all  --  docker0 *       0.0.0.0/0            0.0.0.0/0
    0     0 DNAT       tcp  --  !docker0 *       0.0.0.0/0            0.0.0.0/0            tcp dpt:8080 to:172.17.0.2:80
    1   177 POST_docker  all  --  *      docker0  0.0.0.0/0            0.0.0.0/0
Chain POST_docker (1 references)
    3   357 POST_docker_log  all  --  *      *       0.0.0.0/0            0.0.0.0/0
    3   357 POST_docker_deny  all  --  *      *       0.0.0.0/0            0.0.0.0/0
    3   357 POST_docker_allow  all  --  *      *       0.0.0.0/0            0.0.0.0/0
Chain POST_docker_allow (1 references)
Chain POST_docker_deny (1 references)
Chain POST_docker_log (1 references)
    0     0 PRE_docker  all  --  docker0 *       0.0.0.0/0            0.0.0.0/0
Chain PRE_docker (1 references)
    0     0 PRE_docker_log  all  --  *      *       0.0.0.0/0            0.0.0.0/0
    0     0 PRE_docker_deny  all  --  *      *       0.0.0.0/0            0.0.0.0/0
    0     0 PRE_docker_allow  all  --  *      *       0.0.0.0/0            0.0.0.0/0
Chain PRE_docker_allow (1 references)
Chain PRE_docker_deny (1 references)

可以看到第三条:当我们在访问主机的8080端口的时候,iptables会把流量转发到172.17.0.2的80端口,从而实现了我们从主机上可以直接访问到容器内的业务。
我们通过curl命令访问一下nginx容器:

[root@localhost ~]# curl http://127.0.0.1:8080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

通过上面的输出可以得知我们已经成功的访问到了nginx服务器。
总体来说,docker是官方实现的标准客户端,dockerd是docker服务端的入口,负责接收客户端发送的指令并返回相应的结果,而docker-init在业务主进程没有进程回收功能时则十分有用,docker-proxy组件则是实现docker网络访问的重要组件。

containerd相关的组件

(1)containerd

  • containerd不仅负责容器生命周期的管理,同时还负责一些其他的功能:
  • 镜像的管理,例如容器运行前从镜像仓库拉取镜像到本地。
  • 接收dockerd的请求,通过适当的参数调用runc启动容器。
  • 管理存储相关的资源。
  • 管理网络相关资源。
    containerd包含一个后台常驻进程,默认的socket路径为/run/containerd/containerd.sock,dockerd通过UNIX套接字向containerd发送请求,containerd接收到请求后负责执行相关的动作并把执行结果返回给dockerd。如果不想使用dockerd,也可以直接使用containerd来管理容器,由于containerd更加简单和轻量,生产环境中越来越多的人开始直接使用containerd来管理容器。

2)containerd-shim

containerd-shim的意思是垫片,类似于拧螺丝时夹在螺丝和螺母中之间的垫片。containerd-shim的主要作用是将containerd和真正的容器进程解耦,使用containerd-shim作为容器进程的父进程,从而实现重启dockerd或containerd不影响已经启动的容器进程。

(3)ctr

ctr实际上是containerd-ctr,他是containerd的客户端,主要用来开发和调试,在没有dockerd的环境中,ctr可以充当docker客户端的部分角色,直接向containerd守护进程发送操作容器的请求。

容器运行时组件runc

runc 是一个标准的 OCI 容器运行时的实现,它是一个命令行工具,可以直接用来创建和运行容器。下面我们通过一个实例来演示一下 runc 的神奇之处。

第一步,准备容器运行时文件:进入 /root 目录下,创建 runc 文件夹,并导入 busybox 镜像文件。

[root@localhost ~]# mkdir /root/runc
导入 rootfs 镜像文件
mkdir rootfs && docker export $(docker create busybox) | tar -C rootfs -xvf -

第二步,生成 runc config 文件。我们可以使用 runc spec 命令根据文件系统生成对应的 config.json 文件。命令如下:

runc spec

此时会在当前目录下生成 config.json 文件,我们可以使用 cat 命令查看一下 config.json 的内容:

$ cat config.json
{
    "ociVersion": "1.0.1-dev",
    "process": {
        "terminal": true,
        "user": {
            "uid": 0,
            "gid": 0
        },
        "args": [
            "sh"
        ],
        "env": [
            "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
            "TERM=xterm"
        ],

//后面部分省略...

config.json 文件定义了 runc 启动容器时的一些配置,如根目录的路径,文件挂载路径等配置。
第三步,使用 runc 启动容器。我们可以使用 runc run 命令直接启动 busybox 容器。

[root@localhost ~]# runc run busybox
/ #

此时,我们已经创建并启动了一个 busybox 容器。
我们新打开一个命令行窗口,可以使用 run list 命令看到刚才启动的容器。

[root@localhost ~]# runc list
ID          PID         STATUS      BUNDLE      CREATED                          OWNER
busybox     5355        running     /root       2022-04-22T07:55:26.622779845Z   root

通过上面的输出,我们可以看到,当前已经有一个 busybox 容器处于运行状态。
总体来说,Docker 的组件虽然很多,但每个组件都有自己清晰的工作职责,Docker 相关的组件负责发送和接受 Docker 请求,contianerd 相关的组件负责管理容器的生命周期,而 runc 负责真正意义上创建和启动容器。这些组件相互配合,才使得 Docker 顺利完成了容器的管理工作。

Docker 各组件之间的关系#

首先通过以下命令来启动一个 busybox 容器:

$ docker run -d busybox sleep 3600

容器启动后,通过以下命令查看一下 dockerd 的 PID:

$ sudo ps aux |grep dockerd
root      4147  0.3  0.2 1447892 83236 ?       Ssl  Jul09 245:59 /usr/bin/dockerd

通过上面的输出结果可以得知 dockerd 的 PID 为 4147。为了验证图 3 中 Docker 各组件之间的调用关系,下面使用 pstree 命令查看一下进程父子关系:

$ sudo pstree -l -a -A 4147
dockerd
  |-containerd --config /var/run/docker/containerd/containerd.toml --log-level info
  |   |-containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/d14d20507073e5743e607efd616571c834f1a914f903db6279b8de4b5ba3a45a -address /var/run/docker/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc
  |   |   |-sleep 3600

事实上,dockerd 启动的时候, containerd 就随之启动了,dockerd 与 containerd 一直存在。当执行 docker run 命令(通过 busybox 镜像创建并启动容器)时,containerd 会创建 containerd-shim 充当 “垫片”进程,然后启动容器的真正进程 sleep 3600 。(containerd-shim 的主要作用是将 containerd 和真正的容器进程解耦,使用 containerd-shim 作为容器进程的父进程,从而实现重启 dockerd或containerd 不影响已经启动的容器进程)。这个过程和架构图是完全一致的。
注意: docker 19.03.12 版本的 dockerd 和containerd 组件已经不是父子关系,可以使用以下命令查看,sudo ps aux |grep containerd , 然后使用 pstree 查看 containerd 的 PID。

4.docker 核心理念

docker 的三大理念:build(构建)、ship(运输)、run(运行)。docker 采用 c/s 架构。

docker 的组成部分:

Docker 主机(Host):一个物理机或虚拟主机,用于运行 Docker 服务进程和容器。

Docker 服务端(Server):Docker 守护进程,运行 docker 容器。

Docker 客户端(Client):客户端使用 docker 命令或其他工具调用 docker API。

Docker 仓库(Registry):保存镜像的仓库。分为远程仓库(docker在全世界范围维护一个唯一的远程仓库)和本地仓库(当前自己安装有docker下载的镜像)。

Docker 镜像(Images):镜像可以理解为常见实例使用的模板。只有有了镜像才会有多个容器。一个镜像代表一个软件(如:mysql镜像,redis镜像,nginx镜像)。而镜像启动后就称为一个容器(服务),一个镜像可以启动N个容器(服务)。镜像的特点是只读。

Docker 容器(Container):容器时从镜像生成对外提供服务的一个或一组服务。容器的特点是可读可写。而不会修改镜像。

图片

图片

5.docker镜像的原理

我们一直以来,使用vmare虚拟机安装的系统,是一个完整的系统文件,包括2个部分:

  • liunx内核:作用是提供操作系统的基本功能,和硬件机器交互。
  • centos7发行版:作用是提供软件功能,例如yum安装包管理等。
    图片

所以,linux内核+centos发行版就组成了一个系统,让用户使用。

那么,是否有一个办法,可以灵活的替换发行版,让我们使用不同的系统?

于是docker利用的技术就是共用宿主机的内核实现发行版本的替换。

图片

快速实践,docker下载多个发行版的linux操作系统,查看宿主机系统内核是否和下载的发行版的内核版本一致:

宿主机的内核版本:

root@wtdata-virtual-machine:~# uname -r
5.11.0-27-generic

docker下载的发行版镜像:

ubuntu:
root@47462c73e5b7:/# uname -r
5.11.0-27-generic
debian:
root@7e17d2b83aae:/# uname -r
5.11.0-27-generic
centos:
[root@678680aba2af /]# uname -r
5.11.0-27-generic

docker镜像分层原理:

我们在获取redis的时候。发现下载了多行的信息,最终得到了一个完整的镜像。

root@localhost:~# docker pull redis
Using default tag: latest
latest: Pulling from library/redis
a2abf6c4d29d: Already exists
c7a4e4382001: Pull complete
4044b9ba67c9: Pull complete
c8388a79482f: Pull complete
413c8bb60be2: Pull complete
1abfd3011519: Pull complete

docker通过联合文件系统,将上述的不同的每一层,整合为一个文件系统,为用户隐藏了多层的视角。

图片

第一层:在我们输入docker pull redis的时候,第一步先加载宿主机的linux内核【linux在刚启动的时候会加载bootfs(包含bootloader和kernel。bootloader主要的作用是引导加载kernel)文件系统】。

第二层:当内核加载完成后,docker进行拉取基础镜像也就是linux发行版(Rootfs:在bootfs之上包含的就是典型linux系统中的/dev、/proc、/bin、/etc等标准目录和文件。)。例如这里选择的是centos。

第三、四层:使用centos提供的软件管理(yum)这个镜像有什么作用就利用yum安装什么软件。例如安装tomcat。

前四层是只读镜像,不能修改。且技术是通过联合文件系统整合成一个文件系统

第五层:运行容器后可读可写。

这一过程就是我们在dockerfile构建镜像的时候的样子。

总结:

1.当通过一个image启动容器时,docker会在该images最顶层添加一个读写文件系统作为容器,然后运行该容器。

2.docker镜像本质是基于UnionFS管理的分层文件系统。

3.docker镜像小的原因是docker镜像只有rootfs和其他镜像层,共用宿主机的linux内核(bootfs),所以很小。

4.为什么下载一个docker的nginx镜像,需要133MB?nginx安装包不是才几兆?

因为docker的nginx镜像是分层的,nginx安装包的确就几M,但是一个运行nginx的镜像文件,依赖于父镜像(上一层)和基础镜像(发行版),所以下载nginx镜像有100多MB。

docker镜像的定义:

如果我们定义一个mysql的镜像,我们会这样做:

  • 获取基础镜像,选择一个发行平台(ubuntu,centos等)
  • 在centos中安装mysql5.6软件
  • 导出镜像并且命名为mysql:5.6镜像文件。
    从上面这个过程中,我们可以感觉到这是一层一层添加的,docker镜像的层级概念就出来了,底层是centos镜像,上层是mysql镜像。centos镜像层属于父镜像。

图片

Docker为什么叫分层镜像?

镜像分享一大好处就是共享资源,例如有多个镜像都来自于同一个base镜像,那么在docker host只需要存储一份base镜像。内存里也只需要加载一份host,即可为多个容器服务的。即多个容器共用一个镜像。即使多个容器共享一个base镜像,某个容器修改了base镜像的内容,例如修改了/etc/下的配置文件,其他容器下的/etc/文件时不会被修改的,修改的动作只在单容器内。所有的修改动作,都只会发生在容器层里。

图片

本文作者:wtdata

本文链接:https://www.cnblogs.com/wtdata123/p/16908623.html

版权声明:本作品采用WTdata知识共享署名-许可协议进行许可。

posted @   wtdata  阅读(460)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.

作词 : Victor Thell/Maria Smith/Darin Zanyar

作曲 : Victor Thell/Maria Smith/Darin Zanyar

Går genom staden tittar i marken

Vill lyfta blicken men ser bara oss två

I alla andra de som ser färger

Lever i nuet men jag lever då

Står här tung som bly

Förstelnad grå staty

Du var så fin

Att du fått allting annat att kännas som en mardröm

Som en mardröm

När du var min

Det var så vackert men nu känns allting som en mardröm

Som en mardröm

Vi var det som lyste mitt i det gråa

Vi va de färger du sa att du aldrig sett

Jag minns det vi hade som ljusa pasteller

Nu suddar du bort det och du tror det är lätt

Försöker springa fram

Kommer ingenstans

Du var så fin

Att du fått allting annat att kännas som en mardröm

Som en mardröm

När du var min

Det var så vackert men nu känns allting som en mardröm

Som en mardröm

Du gav ett hederligt perspektiv

På hur fint livet kunde bli

Jag saknar dig

Du gav ett hederligt perspektiv

På hur fint livet kunde bli

Jag saknar dig

Du var så fin

Att du fått allting annat att kännas som en mardröm

Som en mardröm

När du var min

Det var så vackert men nu känns allting som en mardröm

Som en mardröm

Bara du kan väcka mig

Bara du kan väcka mig

Bara du kan väcka mig ur denna mardröm

Bara du bara du kan väcka mig

Bara du bara du kan väcka mig

Bara du bara du kan väcka mig ur denna mardröm