Instant ebooks textbook (Ebook) Kubernetes Programming with Go: Programming Kubernetes Clients and Operators Using Go and the Kubernetes API by Philippe Martin ISBN 9781484290255, 1484290259 download all chapters
Instant ebooks textbook (Ebook) Kubernetes Programming with Go: Programming Kubernetes Clients and Operators Using Go and the Kubernetes API by Philippe Martin ISBN 9781484290255, 1484290259 download all chapters
com
OR CLICK HERE
DOWLOAD EBOOK
https://ebooknice.com/product/kubernetes-operators-49849748
ebooknice.com
ebooknice.com
ebooknice.com
https://ebooknice.com/product/kubernetes-operators-10842206
ebooknice.com
ebooknice.com
ebooknice.com
Kubernetes Programming
with Go
Programming Kubernetes Clients
and Operators Using
Go and the Kubernetes API
Philippe Martin
Kubernetes Programming with Go: Programming Kubernetes Clients and Operators
Using Go and the Kubernetes API
Philippe Martin
Blanquefort, France
Introduction������������������������������������������������������������������������������������������������������������xix
v
Table of Contents
vi
Table of Contents
Toleration������������������������������������������������������������������������������������������������������������������������������� 65
Well-Known Labels���������������������������������������������������������������������������������������������������������������� 66
Writing Kubernetes Resources in Go������������������������������������������������������������������������������������������ 67
Importing the Package���������������������������������������������������������������������������������������������������������� 67
The TypeMeta Fields�������������������������������������������������������������������������������������������������������������� 68
The ObjectMeta Fields����������������������������������������������������������������������������������������������������������� 69
Spec and Status�������������������������������������������������������������������������������������������������������������������� 76
Comparison with Writing YAML Manifests����������������������������������������������������������������������������� 76
A Complete Example������������������������������������������������������������������������������������������������������������������� 78
Conclusion���������������������������������������������������������������������������������������������������������������������������������� 83
vii
Table of Contents
Conversion��������������������������������������������������������������������������������������������������������������������������� 101
Serialization������������������������������������������������������������������������������������������������������������������������� 103
RESTMapper����������������������������������������������������������������������������������������������������������������������������� 105
Kind to Resource����������������������������������������������������������������������������������������������������������������� 106
Resource to Kind����������������������������������������������������������������������������������������������������������������� 107
Finding Resources��������������������������������������������������������������������������������������������������������������� 107
The DefaultRESTMapper Implementation���������������������������������������������������������������������������� 107
Conclusion�������������������������������������������������������������������������������������������������������������������������������� 108
viii
Table of Contents
x
Table of Contents
xi
Table of Contents
Index��������������������������������������������������������������������������������������������������������������������� 309
xii
About the Author
Philippe Martin has been working with Kubernetes for
five years, first by creating an Operator to deploy video
CDNs into the cloud, later helping companies deploy their
applications into Kubernetes, then writing a Client to help
developers work in a Kubernetes environment. Philippe
has passed the CKAD, CKA, and CKS certifications. He has
extensive experience with distributed systems and open-
source software: he started his career 20 years ago creating
thin clients based on the Linux kernel and open-source
components. He is currently working at Red Hat on the
Development Tools team.
Philippe has been active in the development of Kubernetes, especially its
documentation, and participates in the translation of the official documentation into
French, has edited two reference books about the Kubernetes API and kubectl, and is
responsible for the French translation of the Kubernetes Dashboard. He participated in
Google Season of Docs to create the new Kubernetes API Reference section of the official
documentation and is maintaining it.
xiii
About the Technical Reviewers
Bartosz Majsak writes code for fun and profit while proudly
wearing a red fedora (also known as the Red Hat). He has
been long-time open-source contributor and Java developer
turned into Golang aficionado. Bartosz is overly enthusiastic
about coffee, open source, and speaking at conferences,
not necessarily in that order. One thing that perhaps proves
he is not a total geek is his addiction to alpine skiing (and
running).
xv
Acknowledgments
I would like to thank the whole Anevia “CDN” team who started working with me on
Kubernetes back in 2018: David, Ansou, Hossam, Yassine, Étienne, Jason, and Michaël.
Special thanks to Damien Lucas for initiating this project and for having trusted us with
this challenge.
My discovery of Kubernetes has been much easier and pleasant thanks to the TGIK
channel and its numerous episodes, hosted by Joe Beda, Kris Nova, and many others.
Plus, thanks to all the Kubernetes community for such a great ecosystem!
xvii
Introduction
Back in 2017, I was working for a company building video streaming software. At the end
of that year, a small team, including me, got assigned a new job to work on deploying
the Video CDN developed by the company on Kubernetes. We decided to explore the
concept of Custom Resources and Operators to deploy this CDN.
The current Kubernetes release was 1.9, the concept of Custom Resource Definition
had just been released in 1.7, and the sample-controller repository was the only
documentation we knew of to help build an Operator. The Kubernetes ecosystem,
being especially lively, had tools appearing in the following months, specifically the
Kubebuilder SDK. Thus, our project was launched.
From that moment on, I spent numerous days exploring how to build Operators and
other programs interacting with the Kubernetes API. But the damage was done: I had
started to learn Kubernetes programming from specific to general, and it took me a long
time to fully understand the innards of the Kubernetes API.
I have written this book in the hope that it can teach new Kubernetes developers how
to program, from general to specific, with the Kubernetes API in Go.
Chapters at a Glance
The target reader for this book has some experience working with REST APIs, accessing
them either by HTTP or using clients for specific languages; and has some knowledge of
the Kubernetes platform, essentially as a user—for example, some experience deploying
such APIs or frontend applications with the help of YAML manifests.
xix
Introduction
At this point in the book, the reader should be comfortable with building Go
applications working with native resources of the Kubernetes API.
By the end of the book, the reader should be able to start building Kubernetes
operators in Go and have a very good understanding of what happens behind the scenes.
xx
CHAPTER 1
Kubernetes API
Introduction
Kubernetes is a platform to orchestrate containers operating in the declarative mode.
There are one-thousand-and-one ways to describe how the Kubernetes platform is
constructed. This book focuses on programming with the platform.
The entry point of the Kubernetes platform is the API. This chapter explores the
Kubernetes architecture by highlighting the central role of the Kubernetes API. It then
focuses on the HTTP REST nature of the Kubernetes API, and on the extensions added to
organize the many resources managed by it.
Finally, you will learn how to navigate the reference documentation effectively to be
able to extract the maximum quantity of useful information daily.
1. The API server – this is the central point on the control-plane; the
user and the various pieces of the control-plane contact this API to
create, get, delete, update, and watch resources.
1
© Philippe Martin 2023
P. Martin, Kubernetes Programming with Go, https://doi.org/10.1007/978-1-4842-9026-2_1
Chapter 1 Kubernetes API Introduction
2
Chapter 1 Kubernetes API Introduction
etcd
Kubectl
---
Control-plane
cre
at watch
de e set
up lete,
d
Controllers
wa ate
tch
API Server crea
te
te
dele te
a
upd
tch
wa
wa ate
tch
up
d
Kubelet
watch
Scheduler
no Kube-proxy
de
s
O
penAPI Specification
The Kubernetes API is an HTTP REST API. The Kubernetes team provides a specification
for this API in the OpenAPI format, either in v2 format at https://github.com/
kubernetes/kubernetes/tree/master/api/openapi-spec or in Kubernetes v1.24,
in v3 format, at https://github.com/kubernetes/kubernetes/tree/master/api/
openapi-spec/v3.
These specifications also are accessible from the API Server at these paths:
/openapi/v2 and /openapi/v3.
An OpenAPI specification is made up of various parts and, among these, are a list of
paths and a list of definitions. The paths are the URLs you use to request this API, and
for each path, the specification gives the distinct operations such as get, delete, or post.
Then for each operation, the specification indicates what are the parameters and body
format for the request, and what are the possible response codes and associated body
format for the response.
3
Chapter 1 Kubernetes API Introduction
The parameters and bodies for requests and responses can be either simple types
or, more generally, structures containing data. The list of definitions includes data
structures that help build the parameters and bodies for the operations’ requests and
responses.
Figure 1-2 is a simplified view of a specification for a User API. This API can accept
two different paths: /user/{userId} and /user. The first path, /user/{userId}, can accept
two operations, get and delete, respectively, to receive information about a specific user,
given its user ID; and to delete information about a specific user, given its user ID. The
second path, /user, can accept a single operation, post, to add a new user, given its
information.
In this API, a definition of a structure User is given, describing the information for a
user: its ID, first name, and last name. This data structure is used in the response body of
the get operation on the first path, and in the request body of the post operation on the
second path.
4
Chapter 1 Kubernetes API Introduction
SDWKV
XVHU^XVHU,G`
JHW
SDUDPHWHUV
XVHU,GLQWHJHU
UHTXHVW%RG\ HPSW\
UHVSRQVHV
8VHU
GHOHWH
SDUDPHWHUV
XVHU,GLQWHJHU
UHTXHVW%RG\ HPSW\
UHVSRQVHV
HPSW\
XVHU
SRVW
SDUDPHWHUV HPSW\
UHTXHVW%RG\8VHU
UHVSRQVHV
8VHU
GHILQLWLRQV
8VHU
,'LQWHJHU
)LUVW1DPHVWULQJ
/DVW1DPHVWULQJ
V
erbs and Kinds
The Kubernetes API adds two concepts to this specification: the Kubernetes API Verbs
and the Kubernetes Kinds.
5
Chapter 1 Kubernetes API Introduction
The Kubernetes API Verbs are mapped directly to the operations in the OpenAPI
specification. The defined verbs are get, create, update, patch, delete, list, watch, and
deletecollection. The correspondence with the HTTP verbs can be found in Table 1-1.
get GET
create POST
update PUT
patch PATCH
delete DELETE
list GET
watch GET
deletecollection DELETE
The Kubernetes Kinds are a subset of the definitions in the OpenAPI specification.
When requests are made to the Kubernetes API, data structures are exchanged through
the bodies of requests and responses. These structures share common fields, apiVersion
and kind, to help the participants of the request recognize these structures.
If you wanted to make your User API manage this Kind concept, the User structure
would contain two additional fields, apiVersion and kind—for example, with values v1
and User. To determine whether a definition in the Kubernetes OpenAPI specification
is a Kubernetes Kind, you can look at the x-kubernetes-group-version-kind field of the
definition. If this field is defined, the definition is a kind, and it gives you the values of the
apiVersion and kind fields.
Group-Version-Resource
The Kubernetes API is a REST API, and as a result of that it manages Resources, and the
paths to manage these resources follow the REST naming conventions—that is, by using
a plural name to identify a resource and by grouping these resources.
6
Chapter 1 Kubernetes API Introduction
Because the Kubernetes API manages hundreds of resources, they are grouped
together, and because the API evolves, the resources are versioned. For these reasons,
each resource belongs to a given Group and Version, and each resource is uniquely
identified by a Group-Version-Resource, commonly known as GVR.
To find the various resources in the Kubernetes API, you can browse the OpenAPI
specification to extract the distinct paths. Legacy resources (e.g., pods or nodes) will
have been introduced early in the Kubernetes API and all belong to the group core and
the version v1.
The paths to manage legacy resources cluster-wide follow the format /api/
v1/<plural_resource_name>—for example, /api/v1/nodes to manage nodes. Note
that the core group is not represented in the path. To manage resources in a given
namespace, the path format is /api/v1/namespaces/<namespace_name>/<plural_
resource_name>—for example, /api/v1/namespaces/default/pods to manage pods in
the default namespace.
Newer resources are accessible through paths following the format
/apis/<group>/<version>/<plural_resource_name> or /apis/<group>/<version>/
namespaces/<namespace_name>/<plural_resource_name>.
To summarize, the formats of the various paths to access resources are:
or
7
Chapter 1 Kubernetes API Introduction
or
• /apis/<group>/<version>/namespaces/<ns>/<plural_name> – to
access namespaced resources in a specific namespace
Sub-resources
Following the REST API convention, the resources can have sub-resources. A sub-
resource is a resource that belongs to another and can be accessed by specifying its
name after the name of the resource, as follows:
• /api/v1/<plural>/<res-name>/<sub-resource>
Ex: /api/v1/nodes/node1/status
• /api/v1/namespaces/<ns>/<plural>/<res-name>/<sub-resource>
Ex: /api/v1/namespaces/ns1/pods/pod1/status
• /apis/<group>/<version>/<plural>/<res-name>/<sub-resource>
Ex: /apis/storage.k8s.io/v1/volumeattachments/volatt1/status
• /apis/<grp>/<v>/namespaces/<ns>/<plural>/<name>/<sub-res>
Ex: /apis/apps/v1/namespaces/ns1/deployments/dep1/status
Most Kubernetes resources have a status sub-resource. You can see, when writing
operators, that the operator needs to update the status sub-resource to be able to
indicate the state of this resource observed by the operator. The operations that can
be executed in the status sub-resource are get, patch, and update. The Pod has more
sub-resources, including attach, binding, eviction, exec, log, portforward, and proxy.
These sub-resources are useful for getting information about a specific running pod, or
executing some specific operation on a running pod, and so on.
8
Chapter 1 Kubernetes API Introduction
The resources that can Scale (i.e., deployments, replicasets, etc.) have a scale sub-
resource. The operations that can be executed in the scale sub-resource are get, patch,
and update.
Note that these categories are not part of the Kubernetes API definition but are used
in this website to help inexperienced users find their way into the multitude of available
resources.
9
Chapter 1 Kubernetes API Introduction
To be precise, the name displayed is not the resource name in the REST sense, but
the associated principal kind, as shown in Figure 1-4. For example, when managing
Pods, the resource name used in the REST paths is pods (i.e., lowercase and plural), and
the definition used to exchange information about Pods during HTTP requests is named
Pod (i.e., uppercase and singular). Note that other kinds can be associated with the same
resource. In the example in this chapter, the PodList kind (used to exchange information
about Lists of Pods) also exists.
Figure 1-4. The resources for a specific category, with a short description
10
Chapter 1 Kubernetes API Introduction
The apiVersion indicated in the header can help you write a YAML manifest for a
Deployment resource because you need to specify, for each resource in a Kubernetes
manifest, the apiVersion and kind.
In this case, you know the manifest for a deployment will start with the following:
apiVersion: apps/v1
kind: Deployment
The next header line indicates the import to use when writing Go code. In Chapter 3,
you will see how to use this import when describing resources in Go.
After the header, a list of structure definitions is described, also accessible from the
table of contents for the Deployment documentation page in Figure 1-6. The first one is
the principal kind of the resource, optionally followed by structure definitions that are
used in fields of the first kind.
11
Chapter 1 Kubernetes API Introduction
For example, the Deployment kind contains a spec field, of type DeploymentSpec,
which is described later. Note that DeploymentSpec is not a structure directly
exchanged during HTTP requests, and for that, it is not a kind and does not contain kind
or apiVersion fields.
Following the principal kind, and its associated definitions, other kinds associated
with the resource are displayed. In this case, the DeploymentList kind.
Operations Documentation
The next subject in the API Documentation for a resource is the list of possible
operations on this resource or its sub-resources, also accessible from the table of
contents page (see Figure 1-6). By examining the details for the create operation to
Create a Deployment, as shown in Figure 1-7, you can see the HTTP Request verb and
path to use, the parameters to pass during the request, and the possible responses. The
HTTP verb to use for the request is POST and the path is /apis/apps/v1/namespaces/
{namespace}/deployments.
12
Chapter 1 Kubernetes API Introduction
13
Chapter 1 Kubernetes API Introduction
14
Chapter 1 Kubernetes API Introduction
You also can see that the fields, containers and initContainers, are of the same
type as Container, which is described later on the page and is accessible with a link.
The imagePullSecrets field is of type LocalObjectReference, which is described in the
Common Definitions section and also is accessible through a link.
15
Chapter 1 Kubernetes API Introduction
Conclusion
In this chapter, you have been able to discover the architecture of the Kubernetes
platform, and that the API Server plays a central role. The Kubernetes API is an HTTP
REST API, and the resources are categorized into various versioned groups.
Kinds are specific structures used to exchange data between the API server and the
clients. You can browse, using the official Kubernetes website, the API specifications
in a human-readable form to discover the structure of the various resources and
kinds, the different operations available for each resource and sub-resource, and their
associated verbs.
16
CHAPTER 2
Examining Requests
Before starting to write your own HTTP requests, you can examine with kubectl which
requests are used when executing kubectl commands. This can be achieved by using
the verbose flag, -v, with a value greater than or equal to 6. Table 2-1 shows which
information is displayed at each level.
For example, if you want to know the URL that is called when getting pods for all
namespaces, you can use the following command:
17
© Philippe Martin 2023
P. Martin, Kubernetes Programming with Go, https://doi.org/10.1007/978-1-4842-9026-2_2
Chapter 2 Kubernetes API Operations
In the output of the command, you can see that the path used is /api/v1/pods. Or,
when getting pods in a specific namespace, you can see that the path used is /api/v1/
namespaces/default/pods:
-v 6 yes yes – – – – – 0
-v 7 yes – – yes yes – – 0
-v 8 yes – – yes yes yes - ≤ 1024
-v 9 yes yes yes – – yes yes ≤
10240
-v 10 yes yes yes – – yes yes ∞
Making Requests
This section examines all the possible operations you can do with Kubernetes resources.
$ kubectl proxy
Starting to serve on 127.0.0.1:8001
18
Chapter 2 Kubernetes API Operations
On a new terminal, you can now run your HTTP requests without any
authentication. Next, a HOST variable to access the proxy is defined:
$ HOST=http://127.0.0.1:8001
Creating a Resource
You can create a new resource by first creating a Kubernetes manifest describing this
resource—for example, to create a Pod, you can write:
You then need to pass the resource description into the body of a POST request (note
that the -X POST flag can be omitted because the --data-binary flag is being used). For
example, to create a pod resource use:
$ curl $HOST/api/v1/namespaces/project1/pods
-H "Content-Type: application/yaml"
--data-binary @pod.yaml
Note that the namespace is not indicated in the pod.yaml file. If you add it, you must
specify the same namespace in the YAML file and in the path, or you will get an error—
that is, the namespace of the provided object does not match the namespace sent on the
request.
19
Chapter 2 Kubernetes API Operations
$ curl -X GET
$HOST/api/v1/namespaces/project1/pods/nginx
This will return information about the resource in the JSON format, using the kind
associated with this resource as a structure; in this example, it is a Pod kind. This is
equivalent to running the kubectl command:
Cluster-wide
To get the list of resources cluster-wide, for namespaced or non-namespaced resources;
for example, for the pod resource, use the following:
$ curl $HOST/api/v1/pods
This will return information about the list of pods in all namespaces, using a PodList
kind. This is equivalent to running the kubectl command:
$ curl $HOST/api/v1/namespaces/project1/pods
20
Chapter 2 Kubernetes API Operations
This will return information about the list of pods in the project1 namespace, using a
PodList kind. This is equivalent to running the kubectl command:
This results in pods with labels defined in the metadata part of the resource:
21
Chapter 2 Kubernetes API Operations
mylabel: bar
name: nginx2
[...]
Now, when running a list request, you can define some label selectors to filter these
resources by using the labelSelector query parameter, which can contain a comma-
separated list of selectors.
• Select all the resources defining a specific label, no matter its value;
for example, the mylabel label:
$ curl $HOST/api/v1/namespaces/default/pods?
labelSelector=mylabel
• Select all resources not defining a specific label; for example, the
mylabel label:
$ curl $HOST/api/v1/namespaces/default/pods?
labelSelector=\!mylabel
Note the exclamation point (!) before the label name—the backslash character (\)
is being used because the exclamation point is a special character for the shell.
• Select all resources defining a label with a specific value; for example,
mylabel having the value foo:
$ curl $HOST/api/v1/namespaces/default/pods?
labelSelector=mylabel==foo
or
$ curl $HOST/api/v1/namespaces/default/pods?
labelSelector=mylabel=foo
22
Chapter 2 Kubernetes API Operations
$ curl $HOST/api/v1/namespaces/default/pods?
labelSelector=mylabel\!=foo
Note the exclamation point (!) before the equal sign (=)—the backslash character
(\) is being used because the exclamation point is a special character for the shell.
• Select all resources defining a label with a value in a set of values; for
example, the label mylabel having one of the values foo or baz:
$ curl $HOST/api/v1/namespaces/default/pods?
labelSelector=mylabel+in+(foo,baz)
Note the plus characters (+) that encodes spaces in the URL. The original selector
being: mylabel in (foo,baz).
$ curl $HOST/api/v1/namespaces/default/pods?
labelSelector=mylabel+notin+(foo,baz)
Note the plus characters (+) that encodes spaces in the URL. The original selector
being: mylabel not in (foo,baz).
You can combine several selectors by separating them with a comma. This will act as
an AND operator. For example, to select all resources with a label mylabel defined and a
label otherlabel being equal to bar, you can use the following label selector:
$ curl $HOST/api/v1/namespaces/default/pods?
labelSelector=mylabel,otherlabel==bar
23
Chapter 2 Kubernetes API Operations
involvedObject.fieldPath
involvedObject.kind
involvedObject.name
involvedObject.namespace
involvedObject.resourceVersion
involvedObject.uid
reason
reportingComponent
source
type
core.namespace:
status.phase
core.node:
spec.unschedulable
core.pod:
spec.nodeName
spec.restartPolicy
spec.schedulerName
spec.serviceAccountName
24
Chapter 2 Kubernetes API Operations
status.nominatedNodeName
status.phase
status.podIP
core.replicationcontroller:
status.replicas
core.secret:
type
apps.replicaset:
status.replicas
batch.job:
status.successful
certificates.certificatesigningrequest:
spec.signerName
Now, when running a list request, you can indicate some field selectors to filter
these resources by using the fieldSelector parameter, which can contain a comma-
separated list of selectors.
• Select all resources for which a field has a specific value; for example,
the field status.phase having the value Running:
$ curl $HOST/api/v1/namespaces/default/pods?
fieldSelector=status.phase==Running
or
$ curl $HOST/api/v1/namespaces/default/pods?
fieldSelector=status.phase=Running
• Select all resources for which a field has a value different from a
specific one; for example, the field status.phase having a value
different from Running:
$ curl $HOST/api/v1/namespaces/default/pods?
fieldSelector=status.phase\!=Running
25
Chapter 2 Kubernetes API Operations
Note the exclamation point (!) before the equal sign (=)—the backslash character
(\) is being used because the exclamation point is a special character for the shell.
You can combine several selectors by separating them with a comma. This will act
as an AND operator. For example, to select all pods with a phase being equal to Running
and a restart policy not being Always, you can use this field selector:
$ curl $HOST/api/v1/namespaces/default/pods?
fieldSelector=status.phase==Running,
spec.restartPolicy\!=Always
Deleting a Resource
To delete a resource, you need to specify its name (and namespace for namespaced
resources) in the path and use a DELETE request. For example, to delete a pod, use the
following:
$ curl -X DELETE
$HOST/api/v1/namespaces/project1/pods/nginx
This will return the information about the deleted resource in the JSON format, using
the kind associated with the resource—in this case, a Pod kind.
This is equivalent to running the kubectl command (except that you cannot get
information about the deleted resource, only its name with the -o name flag):
$ curl -X DELETE
$HOST/api/v1/namespaces/project1/pods
26
Chapter 2 Kubernetes API Operations
This will return the information about the deleted resources in the JSON format,
using the List kind associated with the resource; in this example, the PodList kind.
This is equivalent to running the kubectl command (except that you cannot get
information about the deleted resources, only their names with the -o name flag):
Note that it is not possible to delete all resources of a specific kind from all
namespaces in a single request the way you could do it with the kubectl
command: kubectl delete pods --all-namespaces --all.
Updating a Resource
It is possible to replace the complete information about a specific resource by using a
PUT request and specifying the name (and namespace for namespaced resources) in the
path and the new resource information in the body of the request.
To illustrate, you can first define a new deployment, with the following command:
Then, you can create this deployment in the cluster using the following:
$ curl
$HOST/apis/apps/v1/namespaces/project1/deployments
-H "Content-Type: application/yaml"
--data-binary @deploy.yaml
Next, you can create an updated manifest for the deployment; for example, by
updating the image of the container with the following command (this will replace the
image name nginx with nginx:latest):
$ cat deploy.yaml |
sed 's/image: nginx/image: nginx:latest/' >
deploy2.yaml
Finally, you can use the following request to update the deployment into the cluster:
$ curl -X PUT
$HOST/apis/apps/v1/namespaces/project1/deployments/nginx
-H "Content-Type: application/yaml"
--data-binary @deploy2.yaml
28
Other documents randomly have
different content
"Rakkaat juhlatoverit! Vaimoni, jolla ei suotta ole nimenä Eva, on
sanonut minulle: 'Jos aiotte viettää perustajaisjuhlaa Flackelholmilla,
pitää teidän kutsua naiset mukaan, juhla ilman naisia on kuin kukka
ilman tuoksua.' Niinpä olemmekin kutsuneet naiset mukaan,
emmekä kadu sitä. Olemme taaskin kerran saaneet tuta, että mies ja
nainen vasta yhdessä muodostavat kokonaisen ihmisen, ja säälimme
muutamia vanhimpia ystäviämme, että he ovat jääneet puolinaisiksi,
ja muutamia nuorista ystävistämme, että he vielä ovat puolinaisia."
— Peter Nahwer unohti säikähdyksissään imaista piippuansa. —
"Mutta keskessämme on kaksi, kaksi puoliskoa, jotka sopivat yhteen,
ja jotka siis lähimmiten yhtyvätkin, eläkööt Andrees ja hänen
morsiamensa, sulo Ingeborg, eläkööt!"
*****
Pari viikkoa myöhemmin eräänä rauhaisana kesäkuun päivänä —
Frans Strandiger oli vielä Hampurissa — vietettiin häitä
Strandigerkartanossa. Sokea istui nojatuolissaan ihan niiden kahden
vieressä, jotka seisoivat alttarin edessä. Kumartuneena, pää
taipuneena eteenpäin, rauhallinen ilme kapeilla kasvoillaan kuunteli
hän nuoren papin korutonta puhetta. Perästäpäin kun vieraat olivat
jättäneet huoneen, lepäsi Ingeborg polvillaan hänen tuolinsa edessä
ja kätki vaalean päänsä vanhan rouvan syliin.
*****
"Suunnitelmin?"
"Niinhän!"
"No… olen iloinen, ett'et ole ihan haluton tuumaan. Jos saan
sanoa sinulle totuuden: niin olet sinä iloinen, että olet saanut tämän
tarjouksen. Sinä ja Flackelholm, te kuulutte yhteen. Raju ja ankara
olet sinä, raju ja ankara on morsiosi! Olet vielä antava haudatakin
itsesi Flackelholmille!"
Kirje!
Kirje kustannusliikkeeltä Berlinissä! Ei siis käsikirjoitusta!
"Jos hän olisi elänyt tänään", sanoi Haller, "olisi hän kohottanut
etusormeaan, näinikään, kuten hänen oli tapansa", — hän kohotti
etusormeaan ilmaan — "ja olisi sanonut: 'Haller! Ette sittekään ole
oikeassa. Heiderieterit ovat terävää, mutta laiskaa väkeä. Ett'ei hän
nyt vaan tulisi laiskaksi!'"
Heim nauroi.
"Annahan minun istua nyt toki ensin", pakisi tämä. "Ei ole mitään
leikintekoa tuommoinen noin pitkä matka vanhalle vaimolle."
"Nahkatossuissa."
*****
"Mitä tiedät?"
Strandiger naurahti: "Mutta te, neiti Elsa, tiedätte, että minä olen
perin tavallinen mies, vieläpä hieman pelkurikin."
"Miten niin?"
"Ei…" vastasi hän… "ei rikas rouva, vaan rouva, jolla on rohkeutta,
ja joka mielellään asuu Flackelholmilla! Nähkääs te", sanoi hän,
"tuolla on Büsen. Vanhat laivurit, jotka siellä päiväkaudet seisovat
rannalla, tuntevat kyllä lippumerkkini; mutta kuinka kauan viipyy,
ennenkuin sieltä voi olla täällä, jos täällä sattuu hätää ja tautia? Eipä
ole keveätä nuorelle naiselle asua Flackelholmilla. Ja talvella…"
*****
Our website is not just a platform for buying books, but a bridge
connecting readers to the timeless values of culture and wisdom. With
an elegant, user-friendly interface and an intelligent search system,
we are committed to providing a quick and convenient shopping
experience. Additionally, our special promotions and home delivery
services ensure that you save time and fully enjoy the joy of reading.
ebooknice.com